[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> functions.php (source)

   1  <?php
   2  /**
   3   * Main WordPress API
   4   *
   5   * @package WordPress
   6   */
   7  
   8  require ( ABSPATH . WPINC . '/option.php' );
   9  
  10  /**
  11   * Convert given MySQL date string into a different format.
  12   *
  13   * `$format` should be a PHP date format string.
  14   * 'U' and 'G' formats will return a sum of timestamp with timezone offset.
  15   * `$date` is expected to be local time in MySQL format (`Y-m-d H:i:s`).
  16   *
  17   * Historically UTC time could be passed to the function to produce Unix timestamp.
  18   *
  19   * If `$translate` is true then the given date and format string will
  20   * be passed to `wp_date()` for translation.
  21   *
  22   * @since 0.71
  23   *
  24   * @param string $format    Format of the date to return.
  25   * @param string $date      Date string to convert.
  26   * @param bool   $translate Whether the return date should be translated. Default true.
  27   * @return string|int|false Formatted date string or sum of Unix timestamp and timezone offset.
  28   *                          False on failure.
  29   */
  30  function mysql2date( $format, $date, $translate = true ) {
  31      if ( empty( $date ) ) {
  32          return false;
  33      }
  34  
  35      $datetime = date_create( $date, wp_timezone() );
  36  
  37      if ( false === $datetime ) {
  38          return false;
  39      }
  40  
  41      // Returns a sum of timestamp with timezone offset. Ideally should never be used.
  42      if ( 'G' === $format || 'U' === $format ) {
  43          return $datetime->getTimestamp() + $datetime->getOffset();
  44      }
  45  
  46      if ( $translate ) {
  47          return wp_date( $format, $datetime->getTimestamp() );
  48      }
  49  
  50      return $datetime->format( $format );
  51  }
  52  
  53  /**
  54   * Retrieves the current time based on specified type.
  55   *
  56   * The 'mysql' type will return the time in the format for MySQL DATETIME field.
  57   * The 'timestamp' type will return the current timestamp or a sum of timestamp
  58   * and timezone offset, depending on `$gmt`.
  59   * Other strings will be interpreted as PHP date formats (e.g. 'Y-m-d').
  60   *
  61   * If $gmt is set to either '1' or 'true', then both types will use GMT time.
  62   * if $gmt is false, the output is adjusted with the GMT offset in the WordPress option.
  63   *
  64   * @since 1.0.0
  65   *
  66   * @param string   $type Type of time to retrieve. Accepts 'mysql', 'timestamp',
  67   *                       or PHP date format string (e.g. 'Y-m-d').
  68   * @param int|bool $gmt  Optional. Whether to use GMT timezone. Default false.
  69   * @return int|string Integer if $type is 'timestamp', string otherwise.
  70   */
  71  function current_time( $type, $gmt = 0 ) {
  72      // Don't use non-GMT timestamp, unless you know the difference and really need to.
  73      if ( 'timestamp' === $type || 'U' === $type ) {
  74          return $gmt ? time() : time() + (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
  75      }
  76  
  77      if ( 'mysql' === $type ) {
  78          $type = 'Y-m-d H:i:s';
  79      }
  80  
  81      $timezone = $gmt ? new DateTimeZone( 'UTC' ) : wp_timezone();
  82      $datetime = new DateTime( 'now', $timezone );
  83  
  84      return $datetime->format( $type );
  85  }
  86  
  87  /**
  88   * Retrieves the current time as an object with the timezone from settings.
  89   *
  90   * @since 5.3.0
  91   *
  92   * @return DateTimeImmutable Date and time object.
  93   */
  94  function current_datetime() {
  95      return new DateTimeImmutable( 'now', wp_timezone() );
  96  }
  97  
  98  /**
  99   * Retrieves the timezone from site settings as a string.
 100   *
 101   * Uses the `timezone_string` option to get a proper timezone if available,
 102   * otherwise falls back to an offset.
 103   *
 104   * @since 5.3.0
 105   *
 106   * @return string PHP timezone string or a ±HH:MM offset.
 107   */
 108  function wp_timezone_string() {
 109      $timezone_string = get_option( 'timezone_string' );
 110  
 111      if ( $timezone_string ) {
 112          return $timezone_string;
 113      }
 114  
 115      $offset  = (float) get_option( 'gmt_offset' );
 116      $hours   = (int) $offset;
 117      $minutes = ( $offset - $hours );
 118  
 119      $sign      = ( $offset < 0 ) ? '-' : '+';
 120      $abs_hour  = abs( $hours );
 121      $abs_mins  = abs( $minutes * 60 );
 122      $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
 123  
 124      return $tz_offset;
 125  }
 126  
 127  /**
 128   * Retrieves the timezone from site settings as a `DateTimeZone` object.
 129   *
 130   * Timezone can be based on a PHP timezone string or a ±HH:MM offset.
 131   *
 132   * @since 5.3.0
 133   *
 134   * @return DateTimeZone Timezone object.
 135   */
 136  function wp_timezone() {
 137      return new DateTimeZone( wp_timezone_string() );
 138  }
 139  
 140  /**
 141   * Retrieves the date in localized format, based on a sum of Unix timestamp and
 142   * timezone offset in seconds.
 143   *
 144   * If the locale specifies the locale month and weekday, then the locale will
 145   * take over the format for the date. If it isn't, then the date format string
 146   * will be used instead.
 147   *
 148   * Note that due to the way WP typically generates a sum of timestamp and offset
 149   * with `strtotime()`, it implies offset added at a _current_ time, not at the time
 150   * the timestamp represents. Storing such timestamps or calculating them differently
 151   * will lead to invalid output.
 152   *
 153   * @since 0.71
 154   * @since 5.3.0 Converted into a wrapper for wp_date().
 155   *
 156   * @global WP_Locale $wp_locale WordPress date and time locale object.
 157   *
 158   * @param string   $format                Format to display the date.
 159   * @param int|bool $timestamp_with_offset Optional. A sum of Unix timestamp and timezone offset
 160   *                                        in seconds. Default false.
 161   * @param bool     $gmt                   Optional. Whether to use GMT timezone. Only applies
 162   *                                        if timestamp is not provided. Default false.
 163   * @return string The date, translated if locale specifies it.
 164   */
 165  function date_i18n( $format, $timestamp_with_offset = false, $gmt = false ) {
 166      $timestamp = $timestamp_with_offset;
 167  
 168      // If timestamp is omitted it should be current time (summed with offset, unless `$gmt` is true).
 169      if ( ! is_numeric( $timestamp ) ) {
 170          $timestamp = current_time( 'timestamp', $gmt );
 171      }
 172  
 173      /*
 174       * This is a legacy implementation quirk that the returned timestamp is also with offset.
 175       * Ideally this function should never be used to produce a timestamp.
 176       */
 177      if ( 'U' === $format ) {
 178          $date = $timestamp;
 179      } elseif ( $gmt && false === $timestamp_with_offset ) { // Current time in UTC.
 180          $date = wp_date( $format, null, new DateTimeZone( 'UTC' ) );
 181      } elseif ( false === $timestamp_with_offset ) { // Current time in site's timezone.
 182          $date = wp_date( $format );
 183      } else {
 184          /*
 185           * Timestamp with offset is typically produced by a UTC `strtotime()` call on an input without timezone.
 186           * This is the best attempt to reverse that operation into a local time to use.
 187           */
 188          $local_time = gmdate( 'Y-m-d H:i:s', $timestamp );
 189          $timezone   = wp_timezone();
 190          $datetime   = date_create( $local_time, $timezone );
 191          $date       = wp_date( $format, $datetime->getTimestamp(), $timezone );
 192      }
 193  
 194      /**
 195       * Filters the date formatted based on the locale.
 196       *
 197       * @since 2.8.0
 198       *
 199       * @param string $date      Formatted date string.
 200       * @param string $format    Format to display the date.
 201       * @param int    $timestamp A sum of Unix timestamp and timezone offset in seconds.
 202       *                          Might be without offset if input omitted timestamp but requested GMT.
 203       * @param bool   $gmt       Whether to use GMT timezone. Only applies if timestamp was not provided.
 204       *                          Default false.
 205       */
 206      $date = apply_filters( 'date_i18n', $date, $format, $timestamp, $gmt );
 207  
 208      return $date;
 209  }
 210  
 211  /**
 212   * Retrieves the date, in localized format.
 213   *
 214   * This is a newer function, intended to replace `date_i18n()` without legacy quirks in it.
 215   *
 216   * Note that, unlike `date_i18n()`, this function accepts a true Unix timestamp, not summed
 217   * with timezone offset.
 218   *
 219   * @since 5.3.0
 220   *
 221   * @param string       $format    PHP date format.
 222   * @param int          $timestamp Optional. Unix timestamp. Defaults to current time.
 223   * @param DateTimeZone $timezone  Optional. Timezone to output result in. Defaults to timezone
 224   *                                from site settings.
 225   * @return string|false The date, translated if locale specifies it. False on invalid timestamp input.
 226   */
 227  function wp_date( $format, $timestamp = null, $timezone = null ) {
 228      global $wp_locale;
 229  
 230      if ( null === $timestamp ) {
 231          $timestamp = time();
 232      } elseif ( ! is_numeric( $timestamp ) ) {
 233          return false;
 234      }
 235  
 236      if ( ! $timezone ) {
 237          $timezone = wp_timezone();
 238      }
 239  
 240      $datetime = date_create( '@' . $timestamp );
 241      $datetime->setTimezone( $timezone );
 242  
 243      if ( empty( $wp_locale->month ) || empty( $wp_locale->weekday ) ) {
 244          $date = $datetime->format( $format );
 245      } else {
 246          // We need to unpack shorthand `r` format because it has parts that might be localized.
 247          $format = preg_replace( '/(?<!\\\\)r/', DATE_RFC2822, $format );
 248  
 249          $new_format    = '';
 250          $format_length = strlen( $format );
 251          $month         = $wp_locale->get_month( $datetime->format( 'm' ) );
 252          $weekday       = $wp_locale->get_weekday( $datetime->format( 'w' ) );
 253  
 254          for ( $i = 0; $i < $format_length; $i ++ ) {
 255              switch ( $format[ $i ] ) {
 256                  case 'D':
 257                      $new_format .= addcslashes( $wp_locale->get_weekday_abbrev( $weekday ), '\\A..Za..z' );
 258                      break;
 259                  case 'F':
 260                      $new_format .= addcslashes( $month, '\\A..Za..z' );
 261                      break;
 262                  case 'l':
 263                      $new_format .= addcslashes( $weekday, '\\A..Za..z' );
 264                      break;
 265                  case 'M':
 266                      $new_format .= addcslashes( $wp_locale->get_month_abbrev( $month ), '\\A..Za..z' );
 267                      break;
 268                  case 'a':
 269                      $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'a' ) ), '\\A..Za..z' );
 270                      break;
 271                  case 'A':
 272                      $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'A' ) ), '\\A..Za..z' );
 273                      break;
 274                  case '\\':
 275                      $new_format .= $format[ $i ];
 276  
 277                      // If character follows a slash, we add it without translating.
 278                      if ( $i < $format_length ) {
 279                          $new_format .= $format[ ++$i ];
 280                      }
 281                      break;
 282                  default:
 283                      $new_format .= $format[ $i ];
 284                      break;
 285              }
 286          }
 287  
 288          $date = $datetime->format( $new_format );
 289          $date = wp_maybe_decline_date( $date );
 290      }
 291  
 292      /**
 293       * Filters the date formatted based on the locale.
 294       *
 295       * @since 5.3.0
 296       *
 297       * @param string       $date      Formatted date string.
 298       * @param string       $format    Format to display the date.
 299       * @param int          $timestamp Unix timestamp.
 300       * @param DateTimeZone $timezone  Timezone.
 301       *
 302       */
 303      $date = apply_filters( 'wp_date', $date, $format, $timestamp, $timezone );
 304  
 305      return $date;
 306  }
 307  
 308  /**
 309   * Determines if the date should be declined.
 310   *
 311   * If the locale specifies that month names require a genitive case in certain
 312   * formats (like 'j F Y'), the month name will be replaced with a correct form.
 313   *
 314   * @since 4.4.0
 315   *
 316   * @global WP_Locale $wp_locale WordPress date and time locale object.
 317   *
 318   * @param string $date Formatted date string.
 319   * @return string The date, declined if locale specifies it.
 320   */
 321  function wp_maybe_decline_date( $date ) {
 322      global $wp_locale;
 323  
 324      // i18n functions are not available in SHORTINIT mode
 325      if ( ! function_exists( '_x' ) ) {
 326          return $date;
 327      }
 328  
 329      /*
 330       * translators: If months in your language require a genitive case,
 331       * translate this to 'on'. Do not translate into your own language.
 332       */
 333      if ( 'on' === _x( 'off', 'decline months names: on or off' ) ) {
 334  
 335          $months          = $wp_locale->month;
 336          $months_genitive = $wp_locale->month_genitive;
 337  
 338          // Match a format like 'j F Y' or 'j. F'
 339          if ( preg_match( '#^\d{1,2}\.? [^\d ]+#u', $date ) ) {
 340  
 341              foreach ( $months as $key => $month ) {
 342                  $months[ $key ] = '# ' . $month . '( |$)#u';
 343              }
 344  
 345              foreach ( $months_genitive as $key => $month ) {
 346                  $months_genitive[ $key ] = ' ' . $month . '$1';
 347              }
 348  
 349              $date = preg_replace( $months, $months_genitive, $date );
 350          }
 351  
 352          // Match a format like 'F jS' or 'F j' and change it to 'j F'
 353          if ( preg_match( '#^[^\d ]+ \d{1,2}(st|nd|rd|th)? #u', trim( $date ) ) ) {
 354              foreach ( $months as $key => $month ) {
 355                  $months[ $key ] = '#' . $month . ' (\d{1,2})(st|nd|rd|th)?#u';
 356              }
 357  
 358              foreach ( $months_genitive as $key => $month ) {
 359                  $months_genitive[ $key ] = '$1 ' . $month;
 360              }
 361  
 362              $date = preg_replace( $months, $months_genitive, $date );
 363          }
 364      }
 365  
 366      // Used for locale-specific rules
 367      $locale = get_locale();
 368  
 369      if ( 'ca' === $locale ) {
 370          // " de abril| de agost| de octubre..." -> " d'abril| d'agost| d'octubre..."
 371          $date = preg_replace( '# de ([ao])#i', " d'\\1", $date );
 372      }
 373  
 374      return $date;
 375  }
 376  
 377  /**
 378   * Convert float number to format based on the locale.
 379   *
 380   * @since 2.3.0
 381   *
 382   * @global WP_Locale $wp_locale WordPress date and time locale object.
 383   *
 384   * @param float $number   The number to convert based on locale.
 385   * @param int   $decimals Optional. Precision of the number of decimal places. Default 0.
 386   * @return string Converted number in string format.
 387   */
 388  function number_format_i18n( $number, $decimals = 0 ) {
 389      global $wp_locale;
 390  
 391      if ( isset( $wp_locale ) ) {
 392          $formatted = number_format( $number, absint( $decimals ), $wp_locale->number_format['decimal_point'], $wp_locale->number_format['thousands_sep'] );
 393      } else {
 394          $formatted = number_format( $number, absint( $decimals ) );
 395      }
 396  
 397      /**
 398       * Filters the number formatted based on the locale.
 399       *
 400       * @since 2.8.0
 401       * @since 4.9.0 The `$number` and `$decimals` parameters were added.
 402       *
 403       * @param string $formatted Converted number in string format.
 404       * @param float  $number    The number to convert based on locale.
 405       * @param int    $decimals  Precision of the number of decimal places.
 406       */
 407      return apply_filters( 'number_format_i18n', $formatted, $number, $decimals );
 408  }
 409  
 410  /**
 411   * Convert number of bytes largest unit bytes will fit into.
 412   *
 413   * It is easier to read 1 KB than 1024 bytes and 1 MB than 1048576 bytes. Converts
 414   * number of bytes to human readable number by taking the number of that unit
 415   * that the bytes will go into it. Supports TB value.
 416   *
 417   * Please note that integers in PHP are limited to 32 bits, unless they are on
 418   * 64 bit architecture, then they have 64 bit size. If you need to place the
 419   * larger size then what PHP integer type will hold, then use a string. It will
 420   * be converted to a double, which should always have 64 bit length.
 421   *
 422   * Technically the correct unit names for powers of 1024 are KiB, MiB etc.
 423   *
 424   * @since 2.3.0
 425   *
 426   * @param int|string $bytes    Number of bytes. Note max integer size for integers.
 427   * @param int        $decimals Optional. Precision of number of decimal places. Default 0.
 428   * @return string|false False on failure. Number string on success.
 429   */
 430  function size_format( $bytes, $decimals = 0 ) {
 431      $quant = array(
 432          'TB' => TB_IN_BYTES,
 433          'GB' => GB_IN_BYTES,
 434          'MB' => MB_IN_BYTES,
 435          'KB' => KB_IN_BYTES,
 436          'B'  => 1,
 437      );
 438  
 439      if ( 0 === $bytes ) {
 440          return number_format_i18n( 0, $decimals ) . ' B';
 441      }
 442  
 443      foreach ( $quant as $unit => $mag ) {
 444          if ( doubleval( $bytes ) >= $mag ) {
 445              return number_format_i18n( $bytes / $mag, $decimals ) . ' ' . $unit;
 446          }
 447      }
 448  
 449      return false;
 450  }
 451  
 452  /**
 453   * Convert a duration to human readable format.
 454   *
 455   * @since 5.1.0
 456   *
 457   * @param string $duration Duration will be in string format (HH:ii:ss) OR (ii:ss),
 458   *                         with a possible prepended negative sign (-).
 459   * @return string|false A human readable duration string, false on failure.
 460   */
 461  function human_readable_duration( $duration = '' ) {
 462      if ( ( empty( $duration ) || ! is_string( $duration ) ) ) {
 463          return false;
 464      }
 465  
 466      $duration = trim( $duration );
 467  
 468      // Remove prepended negative sign.
 469      if ( '-' === substr( $duration, 0, 1 ) ) {
 470          $duration = substr( $duration, 1 );
 471      }
 472  
 473      // Extract duration parts.
 474      $duration_parts = array_reverse( explode( ':', $duration ) );
 475      $duration_count = count( $duration_parts );
 476  
 477      $hour   = null;
 478      $minute = null;
 479      $second = null;
 480  
 481      if ( 3 === $duration_count ) {
 482          // Validate HH:ii:ss duration format.
 483          if ( ! ( (bool) preg_match( '/^([0-9]+):([0-5]?[0-9]):([0-5]?[0-9])$/', $duration ) ) ) {
 484              return false;
 485          }
 486          // Three parts: hours, minutes & seconds.
 487          list( $second, $minute, $hour ) = $duration_parts;
 488      } elseif ( 2 === $duration_count ) {
 489          // Validate ii:ss duration format.
 490          if ( ! ( (bool) preg_match( '/^([0-5]?[0-9]):([0-5]?[0-9])$/', $duration ) ) ) {
 491              return false;
 492          }
 493          // Two parts: minutes & seconds.
 494          list( $second, $minute ) = $duration_parts;
 495      } else {
 496          return false;
 497      }
 498  
 499      $human_readable_duration = array();
 500  
 501      // Add the hour part to the string.
 502      if ( is_numeric( $hour ) ) {
 503          /* translators: %s: Time duration in hour or hours. */
 504          $human_readable_duration[] = sprintf( _n( '%s hour', '%s hours', $hour ), (int) $hour );
 505      }
 506  
 507      // Add the minute part to the string.
 508      if ( is_numeric( $minute ) ) {
 509          /* translators: %s: Time duration in minute or minutes. */
 510          $human_readable_duration[] = sprintf( _n( '%s minute', '%s minutes', $minute ), (int) $minute );
 511      }
 512  
 513      // Add the second part to the string.
 514      if ( is_numeric( $second ) ) {
 515          /* translators: %s: Time duration in second or seconds. */
 516          $human_readable_duration[] = sprintf( _n( '%s second', '%s seconds', $second ), (int) $second );
 517      }
 518  
 519      return implode( ', ', $human_readable_duration );
 520  }
 521  
 522  /**
 523   * Get the week start and end from the datetime or date string from MySQL.
 524   *
 525   * @since 0.71
 526   *
 527   * @param string     $mysqlstring   Date or datetime field type from MySQL.
 528   * @param int|string $start_of_week Optional. Start of the week as an integer. Default empty string.
 529   * @return array Keys are 'start' and 'end'.
 530   */
 531  function get_weekstartend( $mysqlstring, $start_of_week = '' ) {
 532      // MySQL string year.
 533      $my = substr( $mysqlstring, 0, 4 );
 534  
 535      // MySQL string month.
 536      $mm = substr( $mysqlstring, 8, 2 );
 537  
 538      // MySQL string day.
 539      $md = substr( $mysqlstring, 5, 2 );
 540  
 541      // The timestamp for MySQL string day.
 542      $day = mktime( 0, 0, 0, $md, $mm, $my );
 543  
 544      // The day of the week from the timestamp.
 545      $weekday = gmdate( 'w', $day );
 546  
 547      if ( ! is_numeric( $start_of_week ) ) {
 548          $start_of_week = get_option( 'start_of_week' );
 549      }
 550  
 551      if ( $weekday < $start_of_week ) {
 552          $weekday += 7;
 553      }
 554  
 555      // The most recent week start day on or before $day.
 556      $start = $day - DAY_IN_SECONDS * ( $weekday - $start_of_week );
 557  
 558      // $start + 1 week - 1 second.
 559      $end = $start + WEEK_IN_SECONDS - 1;
 560      return compact( 'start', 'end' );
 561  }
 562  
 563  /**
 564   * Unserialize value only if it was serialized.
 565   *
 566   * @since 2.0.0
 567   *
 568   * @param string $original Maybe unserialized original, if is needed.
 569   * @return mixed Unserialized data can be any type.
 570   */
 571  function maybe_unserialize( $original ) {
 572      if ( is_serialized( $original ) ) { // don't attempt to unserialize data that wasn't serialized going in
 573          return @unserialize( $original );
 574      }
 575      return $original;
 576  }
 577  
 578  /**
 579   * Check value to find if it was serialized.
 580   *
 581   * If $data is not an string, then returned value will always be false.
 582   * Serialized data is always a string.
 583   *
 584   * @since 2.0.5
 585   *
 586   * @param string $data   Value to check to see if was serialized.
 587   * @param bool   $strict Optional. Whether to be strict about the end of the string. Default true.
 588   * @return bool False if not serialized and true if it was.
 589   */
 590  function is_serialized( $data, $strict = true ) {
 591      // if it isn't a string, it isn't serialized.
 592      if ( ! is_string( $data ) ) {
 593          return false;
 594      }
 595      $data = trim( $data );
 596      if ( 'N;' == $data ) {
 597          return true;
 598      }
 599      if ( strlen( $data ) < 4 ) {
 600          return false;
 601      }
 602      if ( ':' !== $data[1] ) {
 603          return false;
 604      }
 605      if ( $strict ) {
 606          $lastc = substr( $data, -1 );
 607          if ( ';' !== $lastc && '}' !== $lastc ) {
 608              return false;
 609          }
 610      } else {
 611          $semicolon = strpos( $data, ';' );
 612          $brace     = strpos( $data, '}' );
 613          // Either ; or } must exist.
 614          if ( false === $semicolon && false === $brace ) {
 615              return false;
 616          }
 617          // But neither must be in the first X characters.
 618          if ( false !== $semicolon && $semicolon < 3 ) {
 619              return false;
 620          }
 621          if ( false !== $brace && $brace < 4 ) {
 622              return false;
 623          }
 624      }
 625      $token = $data[0];
 626      switch ( $token ) {
 627          case 's':
 628              if ( $strict ) {
 629                  if ( '"' !== substr( $data, -2, 1 ) ) {
 630                      return false;
 631                  }
 632              } elseif ( false === strpos( $data, '"' ) ) {
 633                  return false;
 634              }
 635              // or else fall through
 636          case 'a':
 637          case 'O':
 638              return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
 639          case 'b':
 640          case 'i':
 641          case 'd':
 642              $end = $strict ? '$' : '';
 643              return (bool) preg_match( "/^{$token}:[0-9.E+-]+;$end/", $data );
 644      }
 645      return false;
 646  }
 647  
 648  /**
 649   * Check whether serialized data is of string type.
 650   *
 651   * @since 2.0.5
 652   *
 653   * @param string $data Serialized data.
 654   * @return bool False if not a serialized string, true if it is.
 655   */
 656  function is_serialized_string( $data ) {
 657      // if it isn't a string, it isn't a serialized string.
 658      if ( ! is_string( $data ) ) {
 659          return false;
 660      }
 661      $data = trim( $data );
 662      if ( strlen( $data ) < 4 ) {
 663          return false;
 664      } elseif ( ':' !== $data[1] ) {
 665          return false;
 666      } elseif ( ';' !== substr( $data, -1 ) ) {
 667          return false;
 668      } elseif ( $data[0] !== 's' ) {
 669          return false;
 670      } elseif ( '"' !== substr( $data, -2, 1 ) ) {
 671          return false;
 672      } else {
 673          return true;
 674      }
 675  }
 676  
 677  /**
 678   * Serialize data, if needed.
 679   *
 680   * @since 2.0.5
 681   *
 682   * @param string|array|object $data Data that might be serialized.
 683   * @return mixed A scalar data
 684   */
 685  function maybe_serialize( $data ) {
 686      if ( is_array( $data ) || is_object( $data ) ) {
 687          return serialize( $data );
 688      }
 689  
 690      // Double serialization is required for backward compatibility.
 691      // See https://core.trac.wordpress.org/ticket/12930
 692      // Also the world will end. See WP 3.6.1.
 693      if ( is_serialized( $data, false ) ) {
 694          return serialize( $data );
 695      }
 696  
 697      return $data;
 698  }
 699  
 700  /**
 701   * Retrieve post title from XMLRPC XML.
 702   *
 703   * If the title element is not part of the XML, then the default post title from
 704   * the $post_default_title will be used instead.
 705   *
 706   * @since 0.71
 707   *
 708   * @global string $post_default_title Default XML-RPC post title.
 709   *
 710   * @param string $content XMLRPC XML Request content
 711   * @return string Post title
 712   */
 713  function xmlrpc_getposttitle( $content ) {
 714      global $post_default_title;
 715      if ( preg_match( '/<title>(.+?)<\/title>/is', $content, $matchtitle ) ) {
 716          $post_title = $matchtitle[1];
 717      } else {
 718          $post_title = $post_default_title;
 719      }
 720      return $post_title;
 721  }
 722  
 723  /**
 724   * Retrieve the post category or categories from XMLRPC XML.
 725   *
 726   * If the category element is not found, then the default post category will be
 727   * used. The return type then would be what $post_default_category. If the
 728   * category is found, then it will always be an array.
 729   *
 730   * @since 0.71
 731   *
 732   * @global string $post_default_category Default XML-RPC post category.
 733   *
 734   * @param string $content XMLRPC XML Request content
 735   * @return string|array List of categories or category name.
 736   */
 737  function xmlrpc_getpostcategory( $content ) {
 738      global $post_default_category;
 739      if ( preg_match( '/<category>(.+?)<\/category>/is', $content, $matchcat ) ) {
 740          $post_category = trim( $matchcat[1], ',' );
 741          $post_category = explode( ',', $post_category );
 742      } else {
 743          $post_category = $post_default_category;
 744      }
 745      return $post_category;
 746  }
 747  
 748  /**
 749   * XMLRPC XML content without title and category elements.
 750   *
 751   * @since 0.71
 752   *
 753   * @param string $content XML-RPC XML Request content.
 754   * @return string XMLRPC XML Request content without title and category elements.
 755   */
 756  function xmlrpc_removepostdata( $content ) {
 757      $content = preg_replace( '/<title>(.+?)<\/title>/si', '', $content );
 758      $content = preg_replace( '/<category>(.+?)<\/category>/si', '', $content );
 759      $content = trim( $content );
 760      return $content;
 761  }
 762  
 763  /**
 764   * Use RegEx to extract URLs from arbitrary content.
 765   *
 766   * @since 3.7.0
 767   *
 768   * @param string $content Content to extract URLs from.
 769   * @return string[] Array of URLs found in passed string.
 770   */
 771  function wp_extract_urls( $content ) {
 772      preg_match_all(
 773          "#([\"']?)("
 774              . '(?:([\w-]+:)?//?)'
 775              . '[^\s()<>]+'
 776              . '[.]'
 777              . '(?:'
 778                  . '\([\w\d]+\)|'
 779                  . '(?:'
 780                      . "[^`!()\[\]{};:'\".,<>«»“”‘’\s]|"
 781                      . '(?:[:]\d+)?/?'
 782                  . ')+'
 783              . ')'
 784          . ")\\1#",
 785          $content,
 786          $post_links
 787      );
 788  
 789      $post_links = array_unique( array_map( 'html_entity_decode', $post_links[2] ) );
 790  
 791      return array_values( $post_links );
 792  }
 793  
 794  /**
 795   * Check content for video and audio links to add as enclosures.
 796   *
 797   * Will not add enclosures that have already been added and will
 798   * remove enclosures that are no longer in the post. This is called as
 799   * pingbacks and trackbacks.
 800   *
 801   * @since 1.5.0
 802   * @since 5.3.0 The `$content` parameter was made optional, and the `$post` parameter was
 803   *              updated to accept a post ID or a WP_Post object.
 804   *
 805   * @global wpdb $wpdb WordPress database abstraction object.
 806   *
 807   * @param string         $content Post content. If `null`, the `post_content` field from `$post` is used.
 808   * @param int|WP_Post    $post    Post ID or post object.
 809   * @return null|bool Returns false if post is not found.
 810   */
 811  function do_enclose( $content = null, $post ) {
 812      global $wpdb;
 813  
 814      // @todo Tidy this code and make the debug code optional.
 815      include_once ( ABSPATH . WPINC . '/class-IXR.php' );
 816  
 817      $post = get_post( $post );
 818      if ( ! $post ) {
 819          return false;
 820      }
 821  
 822      if ( null === $content ) {
 823          $content = $post->post_content;
 824      }
 825  
 826      $post_links = array();
 827  
 828      $pung = get_enclosed( $post->ID );
 829  
 830      $post_links_temp = wp_extract_urls( $content );
 831  
 832      foreach ( $pung as $link_test ) {
 833          // Link is no longer in post.
 834          if ( ! in_array( $link_test, $post_links_temp, true ) ) {
 835              $mids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = 'enclosure' AND meta_value LIKE %s", $post->ID, $wpdb->esc_like( $link_test ) . '%' ) );
 836              foreach ( $mids as $mid ) {
 837                  delete_metadata_by_mid( 'post', $mid );
 838              }
 839          }
 840      }
 841  
 842      foreach ( (array) $post_links_temp as $link_test ) {
 843          // If we haven't pung it already.
 844          if ( ! in_array( $link_test, $pung, true ) ) {
 845              $test = @parse_url( $link_test );
 846              if ( false === $test ) {
 847                  continue;
 848              }
 849              if ( isset( $test['query'] ) ) {
 850                  $post_links[] = $link_test;
 851              } elseif ( isset( $test['path'] ) && ( '/' !== $test['path'] ) && ( '' !== $test['path'] ) ) {
 852                  $post_links[] = $link_test;
 853              }
 854          }
 855      }
 856  
 857      /**
 858       * Filters the list of enclosure links before querying the database.
 859       *
 860       * Allows for the addition and/or removal of potential enclosures to save
 861       * to postmeta before checking the database for existing enclosures.
 862       *
 863       * @since 4.4.0
 864       *
 865       * @param string[] $post_links An array of enclosure links.
 866       * @param int      $post_ID    Post ID.
 867       */
 868      $post_links = apply_filters( 'enclosure_links', $post_links, $post->ID );
 869  
 870      foreach ( (array) $post_links as $url ) {
 871          if ( '' !== $url && ! $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = 'enclosure' AND meta_value LIKE %s", $post->ID, $wpdb->esc_like( $url ) . '%' ) ) ) {
 872  
 873              $headers = wp_get_http_headers( $url );
 874              if ( $headers ) {
 875                  $len           = isset( $headers['content-length'] ) ? (int) $headers['content-length'] : 0;
 876                  $type          = isset( $headers['content-type'] ) ? $headers['content-type'] : '';
 877                  $allowed_types = array( 'video', 'audio' );
 878  
 879                  // Check to see if we can figure out the mime type from
 880                  // the extension
 881                  $url_parts = @parse_url( $url );
 882                  if ( false !== $url_parts ) {
 883                      $extension = pathinfo( $url_parts['path'], PATHINFO_EXTENSION );
 884                      if ( ! empty( $extension ) ) {
 885                          foreach ( wp_get_mime_types() as $exts => $mime ) {
 886                              if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
 887                                  $type = $mime;
 888                                  break;
 889                              }
 890                          }
 891                      }
 892                  }
 893  
 894                  if ( in_array( substr( $type, 0, strpos( $type, '/' ) ), $allowed_types, true ) ) {
 895                      add_post_meta( $post->ID, 'enclosure', "$url\n$len\n$mime\n" );
 896                  }
 897              }
 898          }
 899      }
 900  }
 901  
 902  /**
 903   * Retrieve HTTP Headers from URL.
 904   *
 905   * @since 1.5.1
 906   *
 907   * @param string $url        URL to retrieve HTTP headers from.
 908   * @param bool   $deprecated Not Used.
 909   * @return bool|string False on failure, headers on success.
 910   */
 911  function wp_get_http_headers( $url, $deprecated = false ) {
 912      if ( ! empty( $deprecated ) ) {
 913          _deprecated_argument( __FUNCTION__, '2.7.0' );
 914      }
 915  
 916      $response = wp_safe_remote_head( $url );
 917  
 918      if ( is_wp_error( $response ) ) {
 919          return false;
 920      }
 921  
 922      return wp_remote_retrieve_headers( $response );
 923  }
 924  
 925  /**
 926   * Determines whether the publish date of the current post in the loop is different
 927   * from the publish date of the previous post in the loop.
 928   *
 929   * For more information on this and similar theme functions, check out
 930   * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
 931   * Conditional Tags} article in the Theme Developer Handbook.
 932   *
 933   * @since 0.71
 934   *
 935   * @global string $currentday  The day of the current post in the loop.
 936   * @global string $previousday The day of the previous post in the loop.
 937   *
 938   * @return int 1 when new day, 0 if not a new day.
 939   */
 940  function is_new_day() {
 941      global $currentday, $previousday;
 942  
 943      if ( $currentday !== $previousday ) {
 944          return 1;
 945      } else {
 946          return 0;
 947      }
 948  }
 949  
 950  /**
 951   * Build URL query based on an associative and, or indexed array.
 952   *
 953   * This is a convenient function for easily building url queries. It sets the
 954   * separator to '&' and uses _http_build_query() function.
 955   *
 956   * @since 2.3.0
 957   *
 958   * @see _http_build_query() Used to build the query
 959   * @link https://secure.php.net/manual/en/function.http-build-query.php for more on what
 960   *       http_build_query() does.
 961   *
 962   * @param array $data URL-encode key/value pairs.
 963   * @return string URL-encoded string.
 964   */
 965  function build_query( $data ) {
 966      return _http_build_query( $data, null, '&', '', false );
 967  }
 968  
 969  /**
 970   * From php.net (modified by Mark Jaquith to behave like the native PHP5 function).
 971   *
 972   * @since 3.2.0
 973   * @access private
 974   *
 975   * @see https://secure.php.net/manual/en/function.http-build-query.php
 976   *
 977   * @param array|object  $data       An array or object of data. Converted to array.
 978   * @param string        $prefix     Optional. Numeric index. If set, start parameter numbering with it.
 979   *                                  Default null.
 980   * @param string        $sep        Optional. Argument separator; defaults to 'arg_separator.output'.
 981   *                                  Default null.
 982   * @param string        $key        Optional. Used to prefix key name. Default empty.
 983   * @param bool          $urlencode  Optional. Whether to use urlencode() in the result. Default true.
 984   *
 985   * @return string The query string.
 986   */
 987  function _http_build_query( $data, $prefix = null, $sep = null, $key = '', $urlencode = true ) {
 988      $ret = array();
 989  
 990      foreach ( (array) $data as $k => $v ) {
 991          if ( $urlencode ) {
 992              $k = urlencode( $k );
 993          }
 994          if ( is_int( $k ) && $prefix != null ) {
 995              $k = $prefix . $k;
 996          }
 997          if ( ! empty( $key ) ) {
 998              $k = $key . '%5B' . $k . '%5D';
 999          }
1000          if ( $v === null ) {
1001              continue;
1002          } elseif ( $v === false ) {
1003              $v = '0';
1004          }
1005  
1006          if ( is_array( $v ) || is_object( $v ) ) {
1007              array_push( $ret, _http_build_query( $v, '', $sep, $k, $urlencode ) );
1008          } elseif ( $urlencode ) {
1009              array_push( $ret, $k . '=' . urlencode( $v ) );
1010          } else {
1011              array_push( $ret, $k . '=' . $v );
1012          }
1013      }
1014  
1015      if ( null === $sep ) {
1016          $sep = ini_get( 'arg_separator.output' );
1017      }
1018  
1019      return implode( $sep, $ret );
1020  }
1021  
1022  /**
1023   * Retrieves a modified URL query string.
1024   *
1025   * You can rebuild the URL and append query variables to the URL query by using this function.
1026   * There are two ways to use this function; either a single key and value, or an associative array.
1027   *
1028   * Using a single key and value:
1029   *
1030   *     add_query_arg( 'key', 'value', 'http://example.com' );
1031   *
1032   * Using an associative array:
1033   *
1034   *     add_query_arg( array(
1035   *         'key1' => 'value1',
1036   *         'key2' => 'value2',
1037   *     ), 'http://example.com' );
1038   *
1039   * Omitting the URL from either use results in the current URL being used
1040   * (the value of `$_SERVER['REQUEST_URI']`).
1041   *
1042   * Values are expected to be encoded appropriately with urlencode() or rawurlencode().
1043   *
1044   * Setting any query variable's value to boolean false removes the key (see remove_query_arg()).
1045   *
1046   * Important: The return value of add_query_arg() is not escaped by default. Output should be
1047   * late-escaped with esc_url() or similar to help prevent vulnerability to cross-site scripting
1048   * (XSS) attacks.
1049   *
1050   * @since 1.5.0
1051   * @since 5.3.0 Formalized the existing and already documented parameters
1052   *              by adding `...$args` to the function signature.
1053   *
1054   * @param string|array $key   Either a query variable key, or an associative array of query variables.
1055   * @param string       $value Optional. Either a query variable value, or a URL to act upon.
1056   * @param string       $url   Optional. A URL to act upon.
1057   * @return string New URL query string (unescaped).
1058   */
1059  function add_query_arg( ...$args ) {
1060      if ( is_array( $args[0] ) ) {
1061          if ( count( $args ) < 2 || false === $args[1] ) {
1062              $uri = $_SERVER['REQUEST_URI'];
1063          } else {
1064              $uri = $args[1];
1065          }
1066      } else {
1067          if ( count( $args ) < 3 || false === $args[2] ) {
1068              $uri = $_SERVER['REQUEST_URI'];
1069          } else {
1070              $uri = $args[2];
1071          }
1072      }
1073  
1074      $frag = strstr( $uri, '#' );
1075      if ( $frag ) {
1076          $uri = substr( $uri, 0, -strlen( $frag ) );
1077      } else {
1078          $frag = '';
1079      }
1080  
1081      if ( 0 === stripos( $uri, 'http://' ) ) {
1082          $protocol = 'http://';
1083          $uri      = substr( $uri, 7 );
1084      } elseif ( 0 === stripos( $uri, 'https://' ) ) {
1085          $protocol = 'https://';
1086          $uri      = substr( $uri, 8 );
1087      } else {
1088          $protocol = '';
1089      }
1090  
1091      if ( strpos( $uri, '?' ) !== false ) {
1092          list( $base, $query ) = explode( '?', $uri, 2 );
1093          $base                .= '?';
1094      } elseif ( $protocol || strpos( $uri, '=' ) === false ) {
1095          $base  = $uri . '?';
1096          $query = '';
1097      } else {
1098          $base  = '';
1099          $query = $uri;
1100      }
1101  
1102      wp_parse_str( $query, $qs );
1103      $qs = urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
1104      if ( is_array( $args[0] ) ) {
1105          foreach ( $args[0] as $k => $v ) {
1106              $qs[ $k ] = $v;
1107          }
1108      } else {
1109          $qs[ $args[0] ] = $args[1];
1110      }
1111  
1112      foreach ( $qs as $k => $v ) {
1113          if ( $v === false ) {
1114              unset( $qs[ $k ] );
1115          }
1116      }
1117  
1118      $ret = build_query( $qs );
1119      $ret = trim( $ret, '?' );
1120      $ret = preg_replace( '#=(&|$)#', '$1', $ret );
1121      $ret = $protocol . $base . $ret . $frag;
1122      $ret = rtrim( $ret, '?' );
1123      return $ret;
1124  }
1125  
1126  /**
1127   * Removes an item or items from a query string.
1128   *
1129   * @since 1.5.0
1130   *
1131   * @param string|array $key   Query key or keys to remove.
1132   * @param bool|string  $query Optional. When false uses the current URL. Default false.
1133   * @return string New URL query string.
1134   */
1135  function remove_query_arg( $key, $query = false ) {
1136      if ( is_array( $key ) ) { // removing multiple keys
1137          foreach ( $key as $k ) {
1138              $query = add_query_arg( $k, false, $query );
1139          }
1140          return $query;
1141      }
1142      return add_query_arg( $key, false, $query );
1143  }
1144  
1145  /**
1146   * Returns an array of single-use query variable names that can be removed from a URL.
1147   *
1148   * @since 4.4.0
1149   *
1150   * @return string[] An array of parameters to remove from the URL.
1151   */
1152  function wp_removable_query_args() {
1153      $removable_query_args = array(
1154          'activate',
1155          'activated',
1156          'approved',
1157          'deactivate',
1158          'deleted',
1159          'disabled',
1160          'enabled',
1161          'error',
1162          'hotkeys_highlight_first',
1163          'hotkeys_highlight_last',
1164          'locked',
1165          'message',
1166          'same',
1167          'saved',
1168          'settings-updated',
1169          'skipped',
1170          'spammed',
1171          'trashed',
1172          'unspammed',
1173          'untrashed',
1174          'update',
1175          'updated',
1176          'wp-post-new-reload',
1177      );
1178  
1179      /**
1180       * Filters the list of query variables to remove.
1181       *
1182       * @since 4.2.0
1183       *
1184       * @param string[] $removable_query_args An array of query variables to remove from a URL.
1185       */
1186      return apply_filters( 'removable_query_args', $removable_query_args );
1187  }
1188  
1189  /**
1190   * Walks the array while sanitizing the contents.
1191   *
1192   * @since 0.71
1193   *
1194   * @param array $array Array to walk while sanitizing contents.
1195   * @return array Sanitized $array.
1196   */
1197  function add_magic_quotes( $array ) {
1198      foreach ( (array) $array as $k => $v ) {
1199          if ( is_array( $v ) ) {
1200              $array[ $k ] = add_magic_quotes( $v );
1201          } else {
1202              $array[ $k ] = addslashes( $v );
1203          }
1204      }
1205      return $array;
1206  }
1207  
1208  /**
1209   * HTTP request for URI to retrieve content.
1210   *
1211   * @since 1.5.1
1212   *
1213   * @see wp_safe_remote_get()
1214   *
1215   * @param string $uri URI/URL of web page to retrieve.
1216   * @return false|string HTTP content. False on failure.
1217   */
1218  function wp_remote_fopen( $uri ) {
1219      $parsed_url = @parse_url( $uri );
1220  
1221      if ( ! $parsed_url || ! is_array( $parsed_url ) ) {
1222          return false;
1223      }
1224  
1225      $options            = array();
1226      $options['timeout'] = 10;
1227  
1228      $response = wp_safe_remote_get( $uri, $options );
1229  
1230      if ( is_wp_error( $response ) ) {
1231          return false;
1232      }
1233  
1234      return wp_remote_retrieve_body( $response );
1235  }
1236  
1237  /**
1238   * Set up the WordPress query.
1239   *
1240   * @since 2.0.0
1241   *
1242   * @global WP       $wp           Current WordPress environment instance.
1243   * @global WP_Query $wp_query     WordPress Query object.
1244   * @global WP_Query $wp_the_query Copy of the WordPress Query object.
1245   *
1246   * @param string|array $query_vars Default WP_Query arguments.
1247   */
1248  function wp( $query_vars = '' ) {
1249      global $wp, $wp_query, $wp_the_query;
1250      $wp->main( $query_vars );
1251  
1252      if ( ! isset( $wp_the_query ) ) {
1253          $wp_the_query = $wp_query;
1254      }
1255  }
1256  
1257  /**
1258   * Retrieve the description for the HTTP status.
1259   *
1260   * @since 2.3.0
1261   * @since 3.9.0 Added status codes 418, 428, 429, 431, and 511.
1262   * @since 4.5.0 Added status codes 308, 421, and 451.
1263   * @since 5.1.0 Added status code 103.
1264   *
1265   * @global array $wp_header_to_desc
1266   *
1267   * @param int $code HTTP status code.
1268   * @return string Status description if found, an empty string otherwise.
1269   */
1270  function get_status_header_desc( $code ) {
1271      global $wp_header_to_desc;
1272  
1273      $code = absint( $code );
1274  
1275      if ( ! isset( $wp_header_to_desc ) ) {
1276          $wp_header_to_desc = array(
1277              100 => 'Continue',
1278              101 => 'Switching Protocols',
1279              102 => 'Processing',
1280              103 => 'Early Hints',
1281  
1282              200 => 'OK',
1283              201 => 'Created',
1284              202 => 'Accepted',
1285              203 => 'Non-Authoritative Information',
1286              204 => 'No Content',
1287              205 => 'Reset Content',
1288              206 => 'Partial Content',
1289              207 => 'Multi-Status',
1290              226 => 'IM Used',
1291  
1292              300 => 'Multiple Choices',
1293              301 => 'Moved Permanently',
1294              302 => 'Found',
1295              303 => 'See Other',
1296              304 => 'Not Modified',
1297              305 => 'Use Proxy',
1298              306 => 'Reserved',
1299              307 => 'Temporary Redirect',
1300              308 => 'Permanent Redirect',
1301  
1302              400 => 'Bad Request',
1303              401 => 'Unauthorized',
1304              402 => 'Payment Required',
1305              403 => 'Forbidden',
1306              404 => 'Not Found',
1307              405 => 'Method Not Allowed',
1308              406 => 'Not Acceptable',
1309              407 => 'Proxy Authentication Required',
1310              408 => 'Request Timeout',
1311              409 => 'Conflict',
1312              410 => 'Gone',
1313              411 => 'Length Required',
1314              412 => 'Precondition Failed',
1315              413 => 'Request Entity Too Large',
1316              414 => 'Request-URI Too Long',
1317              415 => 'Unsupported Media Type',
1318              416 => 'Requested Range Not Satisfiable',
1319              417 => 'Expectation Failed',
1320              418 => 'I\'m a teapot',
1321              421 => 'Misdirected Request',
1322              422 => 'Unprocessable Entity',
1323              423 => 'Locked',
1324              424 => 'Failed Dependency',
1325              426 => 'Upgrade Required',
1326              428 => 'Precondition Required',
1327              429 => 'Too Many Requests',
1328              431 => 'Request Header Fields Too Large',
1329              451 => 'Unavailable For Legal Reasons',
1330  
1331              500 => 'Internal Server Error',
1332              501 => 'Not Implemented',
1333              502 => 'Bad Gateway',
1334              503 => 'Service Unavailable',
1335              504 => 'Gateway Timeout',
1336              505 => 'HTTP Version Not Supported',
1337              506 => 'Variant Also Negotiates',
1338              507 => 'Insufficient Storage',
1339              510 => 'Not Extended',
1340              511 => 'Network Authentication Required',
1341          );
1342      }
1343  
1344      if ( isset( $wp_header_to_desc[ $code ] ) ) {
1345          return $wp_header_to_desc[ $code ];
1346      } else {
1347          return '';
1348      }
1349  }
1350  
1351  /**
1352   * Set HTTP status header.
1353   *
1354   * @since 2.0.0
1355   * @since 4.4.0 Added the `$description` parameter.
1356   *
1357   * @see get_status_header_desc()
1358   *
1359   * @param int    $code        HTTP status code.
1360   * @param string $description Optional. A custom description for the HTTP status.
1361   */
1362  function status_header( $code, $description = '' ) {
1363      if ( ! $description ) {
1364          $description = get_status_header_desc( $code );
1365      }
1366  
1367      if ( empty( $description ) ) {
1368          return;
1369      }
1370  
1371      $protocol      = wp_get_server_protocol();
1372      $status_header = "$protocol $code $description";
1373      if ( function_exists( 'apply_filters' ) ) {
1374  
1375          /**
1376           * Filters an HTTP status header.
1377           *
1378           * @since 2.2.0
1379           *
1380           * @param string $status_header HTTP status header.
1381           * @param int    $code          HTTP status code.
1382           * @param string $description   Description for the status code.
1383           * @param string $protocol      Server protocol.
1384           */
1385          $status_header = apply_filters( 'status_header', $status_header, $code, $description, $protocol );
1386      }
1387  
1388      if ( ! headers_sent() ) {
1389          header( $status_header, true, $code );
1390      }
1391  }
1392  
1393  /**
1394   * Get the header information to prevent caching.
1395   *
1396   * The several different headers cover the different ways cache prevention
1397   * is handled by different browsers
1398   *
1399   * @since 2.8.0
1400   *
1401   * @return array The associative array of header names and field values.
1402   */
1403  function wp_get_nocache_headers() {
1404      $headers = array(
1405          'Expires'       => 'Wed, 11 Jan 1984 05:00:00 GMT',
1406          'Cache-Control' => 'no-cache, must-revalidate, max-age=0',
1407      );
1408  
1409      if ( function_exists( 'apply_filters' ) ) {
1410          /**
1411           * Filters the cache-controlling headers.
1412           *
1413           * @since 2.8.0
1414           *
1415           * @see wp_get_nocache_headers()
1416           *
1417           * @param array $headers {
1418           *     Header names and field values.
1419           *
1420           *     @type string $Expires       Expires header.
1421           *     @type string $Cache-Control Cache-Control header.
1422           * }
1423           */
1424          $headers = (array) apply_filters( 'nocache_headers', $headers );
1425      }
1426      $headers['Last-Modified'] = false;
1427      return $headers;
1428  }
1429  
1430  /**
1431   * Set the headers to prevent caching for the different browsers.
1432   *
1433   * Different browsers support different nocache headers, so several
1434   * headers must be sent so that all of them get the point that no
1435   * caching should occur.
1436   *
1437   * @since 2.0.0
1438   *
1439   * @see wp_get_nocache_headers()
1440   */
1441  function nocache_headers() {
1442      if ( headers_sent() ) {
1443          return;
1444      }
1445  
1446      $headers = wp_get_nocache_headers();
1447  
1448      unset( $headers['Last-Modified'] );
1449  
1450      header_remove( 'Last-Modified' );
1451  
1452      foreach ( $headers as $name => $field_value ) {
1453          header( "{$name}: {$field_value}" );
1454      }
1455  }
1456  
1457  /**
1458   * Set the headers for caching for 10 days with JavaScript content type.
1459   *
1460   * @since 2.1.0
1461   */
1462  function cache_javascript_headers() {
1463      $expiresOffset = 10 * DAY_IN_SECONDS;
1464  
1465      header( 'Content-Type: text/javascript; charset=' . get_bloginfo( 'charset' ) );
1466      header( 'Vary: Accept-Encoding' ); // Handle proxies
1467      header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expiresOffset ) . ' GMT' );
1468  }
1469  
1470  /**
1471   * Retrieve the number of database queries during the WordPress execution.
1472   *
1473   * @since 2.0.0
1474   *
1475   * @global wpdb $wpdb WordPress database abstraction object.
1476   *
1477   * @return int Number of database queries.
1478   */
1479  function get_num_queries() {
1480      global $wpdb;
1481      return $wpdb->num_queries;
1482  }
1483  
1484  /**
1485   * Whether input is yes or no.
1486   *
1487   * Must be 'y' to be true.
1488   *
1489   * @since 1.0.0
1490   *
1491   * @param string $yn Character string containing either 'y' (yes) or 'n' (no).
1492   * @return bool True if yes, false on anything else.
1493   */
1494  function bool_from_yn( $yn ) {
1495      return ( strtolower( $yn ) == 'y' );
1496  }
1497  
1498  /**
1499   * Load the feed template from the use of an action hook.
1500   *
1501   * If the feed action does not have a hook, then the function will die with a
1502   * message telling the visitor that the feed is not valid.
1503   *
1504   * It is better to only have one hook for each feed.
1505   *
1506   * @since 2.1.0
1507   *
1508   * @global WP_Query $wp_query WordPress Query object.
1509   */
1510  function do_feed() {
1511      global $wp_query;
1512  
1513      $feed = get_query_var( 'feed' );
1514  
1515      // Remove the pad, if present.
1516      $feed = preg_replace( '/^_+/', '', $feed );
1517  
1518      if ( $feed == '' || $feed == 'feed' ) {
1519          $feed = get_default_feed();
1520      }
1521  
1522      if ( ! has_action( "do_feed_{$feed}" ) ) {
1523          wp_die( __( 'ERROR: This is not a valid feed template.' ), '', array( 'response' => 404 ) );
1524      }
1525  
1526      /**
1527       * Fires once the given feed is loaded.
1528       *
1529       * The dynamic portion of the hook name, `$feed`, refers to the feed template name.
1530       * Possible values include: 'rdf', 'rss', 'rss2', and 'atom'.
1531       *
1532       * @since 2.1.0
1533       * @since 4.4.0 The `$feed` parameter was added.
1534       *
1535       * @param bool   $is_comment_feed Whether the feed is a comment feed.
1536       * @param string $feed            The feed name.
1537       */
1538      do_action( "do_feed_{$feed}", $wp_query->is_comment_feed, $feed );
1539  }
1540  
1541  /**
1542   * Load the RDF RSS 0.91 Feed template.
1543   *
1544   * @since 2.1.0
1545   *
1546   * @see load_template()
1547   */
1548  function do_feed_rdf() {
1549      load_template( ABSPATH . WPINC . '/feed-rdf.php' );
1550  }
1551  
1552  /**
1553   * Load the RSS 1.0 Feed Template.
1554   *
1555   * @since 2.1.0
1556   *
1557   * @see load_template()
1558   */
1559  function do_feed_rss() {
1560      load_template( ABSPATH . WPINC . '/feed-rss.php' );
1561  }
1562  
1563  /**
1564   * Load either the RSS2 comment feed or the RSS2 posts feed.
1565   *
1566   * @since 2.1.0
1567   *
1568   * @see load_template()
1569   *
1570   * @param bool $for_comments True for the comment feed, false for normal feed.
1571   */
1572  function do_feed_rss2( $for_comments ) {
1573      if ( $for_comments ) {
1574          load_template( ABSPATH . WPINC . '/feed-rss2-comments.php' );
1575      } else {
1576          load_template( ABSPATH . WPINC . '/feed-rss2.php' );
1577      }
1578  }
1579  
1580  /**
1581   * Load either Atom comment feed or Atom posts feed.
1582   *
1583   * @since 2.1.0
1584   *
1585   * @see load_template()
1586   *
1587   * @param bool $for_comments True for the comment feed, false for normal feed.
1588   */
1589  function do_feed_atom( $for_comments ) {
1590      if ( $for_comments ) {
1591          load_template( ABSPATH . WPINC . '/feed-atom-comments.php' );
1592      } else {
1593          load_template( ABSPATH . WPINC . '/feed-atom.php' );
1594      }
1595  }
1596  
1597  /**
1598   * Displays the default robots.txt file content.
1599   *
1600   * @since 2.1.0
1601   * @since 5.3.0 Remove the "Disallow: /" output if search engine visiblity is
1602   *              discouraged in favor of robots meta HTML tag in wp_no_robots().
1603   */
1604  function do_robots() {
1605      header( 'Content-Type: text/plain; charset=utf-8' );
1606  
1607      /**
1608       * Fires when displaying the robots.txt file.
1609       *
1610       * @since 2.1.0
1611       */
1612      do_action( 'do_robotstxt' );
1613  
1614      $output = "User-agent: *\n";
1615      $public = get_option( 'blog_public' );
1616  
1617      $site_url = parse_url( site_url() );
1618      $path     = ( ! empty( $site_url['path'] ) ) ? $site_url['path'] : '';
1619      $output  .= "Disallow: $path/wp-admin/\n";
1620      $output  .= "Allow: $path/wp-admin/admin-ajax.php\n";
1621  
1622      /**
1623       * Filters the robots.txt output.
1624       *
1625       * @since 3.0.0
1626       *
1627       * @param string $output The robots.txt output.
1628       * @param bool   $public Whether the site is considered "public".
1629       */
1630      echo apply_filters( 'robots_txt', $output, $public );
1631  }
1632  
1633  /**
1634   * Determines whether WordPress is already installed.
1635   *
1636   * The cache will be checked first. If you have a cache plugin, which saves
1637   * the cache values, then this will work. If you use the default WordPress
1638   * cache, and the database goes away, then you might have problems.
1639   *
1640   * Checks for the 'siteurl' option for whether WordPress is installed.
1641   *
1642   * For more information on this and similar theme functions, check out
1643   * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1644   * Conditional Tags} article in the Theme Developer Handbook.
1645   *
1646   * @since 2.1.0
1647   *
1648   * @global wpdb $wpdb WordPress database abstraction object.
1649   *
1650   * @return bool Whether the site is already installed.
1651   */
1652  function is_blog_installed() {
1653      global $wpdb;
1654  
1655      /*
1656       * Check cache first. If options table goes away and we have true
1657       * cached, oh well.
1658       */
1659      if ( wp_cache_get( 'is_blog_installed' ) ) {
1660          return true;
1661      }
1662  
1663      $suppress = $wpdb->suppress_errors();
1664      if ( ! wp_installing() ) {
1665          $alloptions = wp_load_alloptions();
1666      }
1667      // If siteurl is not set to autoload, check it specifically
1668      if ( ! isset( $alloptions['siteurl'] ) ) {
1669          $installed = $wpdb->get_var( "SELECT option_value FROM $wpdb->options WHERE option_name = 'siteurl'" );
1670      } else {
1671          $installed = $alloptions['siteurl'];
1672      }
1673      $wpdb->suppress_errors( $suppress );
1674  
1675      $installed = ! empty( $installed );
1676      wp_cache_set( 'is_blog_installed', $installed );
1677  
1678      if ( $installed ) {
1679          return true;
1680      }
1681  
1682      // If visiting repair.php, return true and let it take over.
1683      if ( defined( 'WP_REPAIRING' ) ) {
1684          return true;
1685      }
1686  
1687      $suppress = $wpdb->suppress_errors();
1688  
1689      /*
1690       * Loop over the WP tables. If none exist, then scratch installation is allowed.
1691       * If one or more exist, suggest table repair since we got here because the
1692       * options table could not be accessed.
1693       */
1694      $wp_tables = $wpdb->tables();
1695      foreach ( $wp_tables as $table ) {
1696          // The existence of custom user tables shouldn't suggest an insane state or prevent a clean installation.
1697          if ( defined( 'CUSTOM_USER_TABLE' ) && CUSTOM_USER_TABLE == $table ) {
1698              continue;
1699          }
1700          if ( defined( 'CUSTOM_USER_META_TABLE' ) && CUSTOM_USER_META_TABLE == $table ) {
1701              continue;
1702          }
1703  
1704          if ( ! $wpdb->get_results( "DESCRIBE $table;" ) ) {
1705              continue;
1706          }
1707  
1708          // One or more tables exist. We are insane.
1709  
1710          wp_load_translations_early();
1711  
1712          // Die with a DB error.
1713          $wpdb->error = sprintf(
1714              /* translators: %s: Database repair URL. */
1715              __( 'One or more database tables are unavailable. The database may need to be <a href="%s">repaired</a>.' ),
1716              'maint/repair.php?referrer=is_blog_installed'
1717          );
1718  
1719          dead_db();
1720      }
1721  
1722      $wpdb->suppress_errors( $suppress );
1723  
1724      wp_cache_set( 'is_blog_installed', false );
1725  
1726      return false;
1727  }
1728  
1729  /**
1730   * Retrieve URL with nonce added to URL query.
1731   *
1732   * @since 2.0.4
1733   *
1734   * @param string     $actionurl URL to add nonce action.
1735   * @param int|string $action    Optional. Nonce action name. Default -1.
1736   * @param string     $name      Optional. Nonce name. Default '_wpnonce'.
1737   * @return string Escaped URL with nonce action added.
1738   */
1739  function wp_nonce_url( $actionurl, $action = -1, $name = '_wpnonce' ) {
1740      $actionurl = str_replace( '&amp;', '&', $actionurl );
1741      return esc_html( add_query_arg( $name, wp_create_nonce( $action ), $actionurl ) );
1742  }
1743  
1744  /**
1745   * Retrieve or display nonce hidden field for forms.
1746   *
1747   * The nonce field is used to validate that the contents of the form came from
1748   * the location on the current site and not somewhere else. The nonce does not
1749   * offer absolute protection, but should protect against most cases. It is very
1750   * important to use nonce field in forms.
1751   *
1752   * The $action and $name are optional, but if you want to have better security,
1753   * it is strongly suggested to set those two parameters. It is easier to just
1754   * call the function without any parameters, because validation of the nonce
1755   * doesn't require any parameters, but since crackers know what the default is
1756   * it won't be difficult for them to find a way around your nonce and cause
1757   * damage.
1758   *
1759   * The input name will be whatever $name value you gave. The input value will be
1760   * the nonce creation value.
1761   *
1762   * @since 2.0.4
1763   *
1764   * @param int|string $action  Optional. Action name. Default -1.
1765   * @param string     $name    Optional. Nonce name. Default '_wpnonce'.
1766   * @param bool       $referer Optional. Whether to set the referer field for validation. Default true.
1767   * @param bool       $echo    Optional. Whether to display or return hidden form field. Default true.
1768   * @return string Nonce field HTML markup.
1769   */
1770  function wp_nonce_field( $action = -1, $name = '_wpnonce', $referer = true, $echo = true ) {
1771      $name        = esc_attr( $name );
1772      $nonce_field = '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . wp_create_nonce( $action ) . '" />';
1773  
1774      if ( $referer ) {
1775          $nonce_field .= wp_referer_field( false );
1776      }
1777  
1778      if ( $echo ) {
1779          echo $nonce_field;
1780      }
1781  
1782      return $nonce_field;
1783  }
1784  
1785  /**
1786   * Retrieve or display referer hidden field for forms.
1787   *
1788   * The referer link is the current Request URI from the server super global. The
1789   * input name is '_wp_http_referer', in case you wanted to check manually.
1790   *
1791   * @since 2.0.4
1792   *
1793   * @param bool $echo Optional. Whether to echo or return the referer field. Default true.
1794   * @return string Referer field HTML markup.
1795   */
1796  function wp_referer_field( $echo = true ) {
1797      $referer_field = '<input type="hidden" name="_wp_http_referer" value="' . esc_attr( wp_unslash( $_SERVER['REQUEST_URI'] ) ) . '" />';
1798  
1799      if ( $echo ) {
1800          echo $referer_field;
1801      }
1802      return $referer_field;
1803  }
1804  
1805  /**
1806   * Retrieve or display original referer hidden field for forms.
1807   *
1808   * The input name is '_wp_original_http_referer' and will be either the same
1809   * value of wp_referer_field(), if that was posted already or it will be the
1810   * current page, if it doesn't exist.
1811   *
1812   * @since 2.0.4
1813   *
1814   * @param bool   $echo         Optional. Whether to echo the original http referer. Default true.
1815   * @param string $jump_back_to Optional. Can be 'previous' or page you want to jump back to.
1816   *                             Default 'current'.
1817   * @return string Original referer field.
1818   */
1819  function wp_original_referer_field( $echo = true, $jump_back_to = 'current' ) {
1820      $ref = wp_get_original_referer();
1821      if ( ! $ref ) {
1822          $ref = 'previous' == $jump_back_to ? wp_get_referer() : wp_unslash( $_SERVER['REQUEST_URI'] );
1823      }
1824      $orig_referer_field = '<input type="hidden" name="_wp_original_http_referer" value="' . esc_attr( $ref ) . '" />';
1825      if ( $echo ) {
1826          echo $orig_referer_field;
1827      }
1828      return $orig_referer_field;
1829  }
1830  
1831  /**
1832   * Retrieve referer from '_wp_http_referer' or HTTP referer.
1833   *
1834   * If it's the same as the current request URL, will return false.
1835   *
1836   * @since 2.0.4
1837   *
1838   * @return false|string False on failure. Referer URL on success.
1839   */
1840  function wp_get_referer() {
1841      if ( ! function_exists( 'wp_validate_redirect' ) ) {
1842          return false;
1843      }
1844  
1845      $ref = wp_get_raw_referer();
1846  
1847      if ( $ref && $ref !== wp_unslash( $_SERVER['REQUEST_URI'] ) && $ref !== home_url() . wp_unslash( $_SERVER['REQUEST_URI'] ) ) {
1848          return wp_validate_redirect( $ref, false );
1849      }
1850  
1851      return false;
1852  }
1853  
1854  /**
1855   * Retrieves unvalidated referer from '_wp_http_referer' or HTTP referer.
1856   *
1857   * Do not use for redirects, use wp_get_referer() instead.
1858   *
1859   * @since 4.5.0
1860   *
1861   * @return string|false Referer URL on success, false on failure.
1862   */
1863  function wp_get_raw_referer() {
1864      if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
1865          return wp_unslash( $_REQUEST['_wp_http_referer'] );
1866      } elseif ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
1867          return wp_unslash( $_SERVER['HTTP_REFERER'] );
1868      }
1869  
1870      return false;
1871  }
1872  
1873  /**
1874   * Retrieve original referer that was posted, if it exists.
1875   *
1876   * @since 2.0.4
1877   *
1878   * @return string|false False if no original referer or original referer if set.
1879   */
1880  function wp_get_original_referer() {
1881      if ( ! empty( $_REQUEST['_wp_original_http_referer'] ) && function_exists( 'wp_validate_redirect' ) ) {
1882          return wp_validate_redirect( wp_unslash( $_REQUEST['_wp_original_http_referer'] ), false );
1883      }
1884      return false;
1885  }
1886  
1887  /**
1888   * Recursive directory creation based on full path.
1889   *
1890   * Will attempt to set permissions on folders.
1891   *
1892   * @since 2.0.1
1893   *
1894   * @param string $target Full path to attempt to create.
1895   * @return bool Whether the path was created. True if path already exists.
1896   */
1897  function wp_mkdir_p( $target ) {
1898      $wrapper = null;
1899  
1900      // Strip the protocol.
1901      if ( wp_is_stream( $target ) ) {
1902          list( $wrapper, $target ) = explode( '://', $target, 2 );
1903      }
1904  
1905      // From php.net/mkdir user contributed notes.
1906      $target = str_replace( '//', '/', $target );
1907  
1908      // Put the wrapper back on the target.
1909      if ( $wrapper !== null ) {
1910          $target = $wrapper . '://' . $target;
1911      }
1912  
1913      /*
1914       * Safe mode fails with a trailing slash under certain PHP versions.
1915       * Use rtrim() instead of untrailingslashit to avoid formatting.php dependency.
1916       */
1917      $target = rtrim( $target, '/' );
1918      if ( empty( $target ) ) {
1919          $target = '/';
1920      }
1921  
1922      if ( file_exists( $target ) ) {
1923          return @is_dir( $target );
1924      }
1925  
1926      // Do not allow path traversals.
1927      if ( false !== strpos( $target, '../' ) || false !== strpos( $target, '..' . DIRECTORY_SEPARATOR ) ) {
1928          return false;
1929      }
1930  
1931      // We need to find the permissions of the parent folder that exists and inherit that.
1932      $target_parent = dirname( $target );
1933      while ( '.' != $target_parent && ! is_dir( $target_parent ) && dirname( $target_parent ) !== $target_parent ) {
1934          $target_parent = dirname( $target_parent );
1935      }
1936  
1937      // Get the permission bits.
1938      $stat = @stat( $target_parent );
1939      if ( $stat ) {
1940          $dir_perms = $stat['mode'] & 0007777;
1941      } else {
1942          $dir_perms = 0777;
1943      }
1944  
1945      if ( @mkdir( $target, $dir_perms, true ) ) {
1946  
1947          /*
1948           * If a umask is set that modifies $dir_perms, we'll have to re-set
1949           * the $dir_perms correctly with chmod()
1950           */
1951          if ( $dir_perms != ( $dir_perms & ~umask() ) ) {
1952              $folder_parts = explode( '/', substr( $target, strlen( $target_parent ) + 1 ) );
1953              for ( $i = 1, $c = count( $folder_parts ); $i <= $c; $i++ ) {
1954                  chmod( $target_parent . '/' . implode( '/', array_slice( $folder_parts, 0, $i ) ), $dir_perms );
1955              }
1956          }
1957  
1958          return true;
1959      }
1960  
1961      return false;
1962  }
1963  
1964  /**
1965   * Test if a given filesystem path is absolute.
1966   *
1967   * For example, '/foo/bar', or 'c:\windows'.
1968   *
1969   * @since 2.5.0
1970   *
1971   * @param string $path File path.
1972   * @return bool True if path is absolute, false is not absolute.
1973   */
1974  function path_is_absolute( $path ) {
1975      /*
1976       * Check to see if the path is a stream and check to see if its an actual
1977       * path or file as realpath() does not support stream wrappers.
1978       */
1979      if ( wp_is_stream( $path ) && ( is_dir( $path ) || is_file( $path ) ) ) {
1980          return true;
1981      }
1982  
1983      /*
1984       * This is definitive if true but fails if $path does not exist or contains
1985       * a symbolic link.
1986       */
1987      if ( realpath( $path ) == $path ) {
1988          return true;
1989      }
1990  
1991      if ( strlen( $path ) == 0 || $path[0] == '.' ) {
1992          return false;
1993      }
1994  
1995      // Windows allows absolute paths like this.
1996      if ( preg_match( '#^[a-zA-Z]:\\\\#', $path ) ) {
1997          return true;
1998      }
1999  
2000      // A path starting with / or \ is absolute; anything else is relative.
2001      return ( $path[0] == '/' || $path[0] == '\\' );
2002  }
2003  
2004  /**
2005   * Join two filesystem paths together.
2006   *
2007   * For example, 'give me $path relative to $base'. If the $path is absolute,
2008   * then it the full path is returned.
2009   *
2010   * @since 2.5.0
2011   *
2012   * @param string $base Base path.
2013   * @param string $path Path relative to $base.
2014   * @return string The path with the base or absolute path.
2015   */
2016  function path_join( $base, $path ) {
2017      if ( path_is_absolute( $path ) ) {
2018          return $path;
2019      }
2020  
2021      return rtrim( $base, '/' ) . '/' . ltrim( $path, '/' );
2022  }
2023  
2024  /**
2025   * Normalize a filesystem path.
2026   *
2027   * On windows systems, replaces backslashes with forward slashes
2028   * and forces upper-case drive letters.
2029   * Allows for two leading slashes for Windows network shares, but
2030   * ensures that all other duplicate slashes are reduced to a single.
2031   *
2032   * @since 3.9.0
2033   * @since 4.4.0 Ensures upper-case drive letters on Windows systems.
2034   * @since 4.5.0 Allows for Windows network shares.
2035   * @since 4.9.7 Allows for PHP file wrappers.
2036   *
2037   * @param string $path Path to normalize.
2038   * @return string Normalized path.
2039   */
2040  function wp_normalize_path( $path ) {
2041      $wrapper = '';
2042      if ( wp_is_stream( $path ) ) {
2043          list( $wrapper, $path ) = explode( '://', $path, 2 );
2044          $wrapper               .= '://';
2045      }
2046  
2047      // Standardise all paths to use /
2048      $path = str_replace( '\\', '/', $path );
2049  
2050      // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
2051      $path = preg_replace( '|(?<=.)/+|', '/', $path );
2052  
2053      // Windows paths should uppercase the drive letter
2054      if ( ':' === substr( $path, 1, 1 ) ) {
2055          $path = ucfirst( $path );
2056      }
2057  
2058      return $wrapper . $path;
2059  }
2060  
2061  /**
2062   * Determine a writable directory for temporary files.
2063   *
2064   * Function's preference is the return value of sys_get_temp_dir(),
2065   * followed by your PHP temporary upload directory, followed by WP_CONTENT_DIR,
2066   * before finally defaulting to /tmp/
2067   *
2068   * In the event that this function does not find a writable location,
2069   * It may be overridden by the WP_TEMP_DIR constant in your wp-config.php file.
2070   *
2071   * @since 2.5.0
2072   *
2073   * @staticvar string $temp
2074   *
2075   * @return string Writable temporary directory.
2076   */
2077  function get_temp_dir() {
2078      static $temp = '';
2079      if ( defined( 'WP_TEMP_DIR' ) ) {
2080          return trailingslashit( WP_TEMP_DIR );
2081      }
2082  
2083      if ( $temp ) {
2084          return trailingslashit( $temp );
2085      }
2086  
2087      if ( function_exists( 'sys_get_temp_dir' ) ) {
2088          $temp = sys_get_temp_dir();
2089          if ( @is_dir( $temp ) && wp_is_writable( $temp ) ) {
2090              return trailingslashit( $temp );
2091          }
2092      }
2093  
2094      $temp = ini_get( 'upload_tmp_dir' );
2095      if ( @is_dir( $temp ) && wp_is_writable( $temp ) ) {
2096          return trailingslashit( $temp );
2097      }
2098  
2099      $temp = WP_CONTENT_DIR . '/';
2100      if ( is_dir( $temp ) && wp_is_writable( $temp ) ) {
2101          return $temp;
2102      }
2103  
2104      return '/tmp/';
2105  }
2106  
2107  /**
2108   * Determine if a directory is writable.
2109   *
2110   * This function is used to work around certain ACL issues in PHP primarily
2111   * affecting Windows Servers.
2112   *
2113   * @since 3.6.0
2114   *
2115   * @see win_is_writable()
2116   *
2117   * @param string $path Path to check for write-ability.
2118   * @return bool Whether the path is writable.
2119   */
2120  function wp_is_writable( $path ) {
2121      if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ) {
2122          return win_is_writable( $path );
2123      } else {
2124          return @is_writable( $path );
2125      }
2126  }
2127  
2128  /**
2129   * Workaround for Windows bug in is_writable() function
2130   *
2131   * PHP has issues with Windows ACL's for determine if a
2132   * directory is writable or not, this works around them by
2133   * checking the ability to open files rather than relying
2134   * upon PHP to interprate the OS ACL.
2135   *
2136   * @since 2.8.0
2137   *
2138   * @see https://bugs.php.net/bug.php?id=27609
2139   * @see https://bugs.php.net/bug.php?id=30931
2140   *
2141   * @param string $path Windows path to check for write-ability.
2142   * @return bool Whether the path is writable.
2143   */
2144  function win_is_writable( $path ) {
2145  
2146      if ( $path[ strlen( $path ) - 1 ] == '/' ) { // if it looks like a directory, check a random file within the directory
2147          return win_is_writable( $path . uniqid( mt_rand() ) . '.tmp' );
2148      } elseif ( is_dir( $path ) ) { // If it's a directory (and not a file) check a random file within the directory
2149          return win_is_writable( $path . '/' . uniqid( mt_rand() ) . '.tmp' );
2150      }
2151      // check tmp file for read/write capabilities
2152      $should_delete_tmp_file = ! file_exists( $path );
2153      $f                      = @fopen( $path, 'a' );
2154      if ( $f === false ) {
2155          return false;
2156      }
2157      fclose( $f );
2158      if ( $should_delete_tmp_file ) {
2159          unlink( $path );
2160      }
2161      return true;
2162  }
2163  
2164  /**
2165   * Retrieves uploads directory information.
2166   *
2167   * Same as wp_upload_dir() but "light weight" as it doesn't attempt to create the uploads directory.
2168   * Intended for use in themes, when only 'basedir' and 'baseurl' are needed, generally in all cases
2169   * when not uploading files.
2170   *
2171   * @since 4.5.0
2172   *
2173   * @see wp_upload_dir()
2174   *
2175   * @return array See wp_upload_dir() for description.
2176   */
2177  function wp_get_upload_dir() {
2178      return wp_upload_dir( null, false );
2179  }
2180  
2181  /**
2182   * Returns an array containing the current upload directory's path and URL.
2183   *
2184   * Checks the 'upload_path' option, which should be from the web root folder,
2185   * and if it isn't empty it will be used. If it is empty, then the path will be
2186   * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
2187   * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
2188   *
2189   * The upload URL path is set either by the 'upload_url_path' option or by using
2190   * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
2191   *
2192   * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
2193   * the administration settings panel), then the time will be used. The format
2194   * will be year first and then month.
2195   *
2196   * If the path couldn't be created, then an error will be returned with the key
2197   * 'error' containing the error message. The error suggests that the parent
2198   * directory is not writable by the server.
2199   *
2200   * @since 2.0.0
2201   * @uses _wp_upload_dir()
2202   *
2203   * @staticvar array $cache
2204   * @staticvar array $tested_paths
2205   *
2206   * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
2207   * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
2208   *                           Default true for backward compatibility.
2209   * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
2210   * @return array {
2211   *     Array of information about the upload directory.
2212   *
2213   *     @type string       $path    Base directory and subdirectory or full path to upload directory.
2214   *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
2215   *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
2216   *     @type string       $basedir Path without subdir.
2217   *     @type string       $baseurl URL path without subdir.
2218   *     @type string|false $error   False or error message.
2219   * }
2220   */
2221  function wp_upload_dir( $time = null, $create_dir = true, $refresh_cache = false ) {
2222      static $cache = array(), $tested_paths = array();
2223  
2224      $key = sprintf( '%d-%s', get_current_blog_id(), (string) $time );
2225  
2226      if ( $refresh_cache || empty( $cache[ $key ] ) ) {
2227          $cache[ $key ] = _wp_upload_dir( $time );
2228      }
2229  
2230      /**
2231       * Filters the uploads directory data.
2232       *
2233       * @since 2.0.0
2234       *
2235       * @param array $uploads {
2236       *     Array of information about the upload directory.
2237       *
2238       *     @type string       $path    Base directory and subdirectory or full path to upload directory.
2239       *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
2240       *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
2241       *     @type string       $basedir Path without subdir.
2242       *     @type string       $baseurl URL path without subdir.
2243       *     @type string|false $error   False or error message.
2244       * }
2245       */
2246      $uploads = apply_filters( 'upload_dir', $cache[ $key ] );
2247  
2248      if ( $create_dir ) {
2249          $path = $uploads['path'];
2250  
2251          if ( array_key_exists( $path, $tested_paths ) ) {
2252              $uploads['error'] = $tested_paths[ $path ];
2253          } else {
2254              if ( ! wp_mkdir_p( $path ) ) {
2255                  if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
2256                      $error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
2257                  } else {
2258                      $error_path = wp_basename( $uploads['basedir'] ) . $uploads['subdir'];
2259                  }
2260  
2261                  $uploads['error'] = sprintf(
2262                      /* translators: %s: Directory path. */
2263                      __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
2264                      esc_html( $error_path )
2265                  );
2266              }
2267  
2268              $tested_paths[ $path ] = $uploads['error'];
2269          }
2270      }
2271  
2272      return $uploads;
2273  }
2274  
2275  /**
2276   * A non-filtered, non-cached version of wp_upload_dir() that doesn't check the path.
2277   *
2278   * @since 4.5.0
2279   * @access private
2280   *
2281   * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
2282   * @return array See wp_upload_dir()
2283   */
2284  function _wp_upload_dir( $time = null ) {
2285      $siteurl     = get_option( 'siteurl' );
2286      $upload_path = trim( get_option( 'upload_path' ) );
2287  
2288      if ( empty( $upload_path ) || 'wp-content/uploads' == $upload_path ) {
2289          $dir = WP_CONTENT_DIR . '/uploads';
2290      } elseif ( 0 !== strpos( $upload_path, ABSPATH ) ) {
2291          // $dir is absolute, $upload_path is (maybe) relative to ABSPATH
2292          $dir = path_join( ABSPATH, $upload_path );
2293      } else {
2294          $dir = $upload_path;
2295      }
2296  
2297      $url = get_option( 'upload_url_path' );
2298      if ( ! $url ) {
2299          if ( empty( $upload_path ) || ( 'wp-content/uploads' == $upload_path ) || ( $upload_path == $dir ) ) {
2300              $url = WP_CONTENT_URL . '/uploads';
2301          } else {
2302              $url = trailingslashit( $siteurl ) . $upload_path;
2303          }
2304      }
2305  
2306      /*
2307       * Honor the value of UPLOADS. This happens as long as ms-files rewriting is disabled.
2308       * We also sometimes obey UPLOADS when rewriting is enabled -- see the next block.
2309       */
2310      if ( defined( 'UPLOADS' ) && ! ( is_multisite() && get_site_option( 'ms_files_rewriting' ) ) ) {
2311          $dir = ABSPATH . UPLOADS;
2312          $url = trailingslashit( $siteurl ) . UPLOADS;
2313      }
2314  
2315      // If multisite (and if not the main site in a post-MU network)
2316      if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
2317  
2318          if ( ! get_site_option( 'ms_files_rewriting' ) ) {
2319              /*
2320               * If ms-files rewriting is disabled (networks created post-3.5), it is fairly
2321               * straightforward: Append sites/%d if we're not on the main site (for post-MU
2322               * networks). (The extra directory prevents a four-digit ID from conflicting with
2323               * a year-based directory for the main site. But if a MU-era network has disabled
2324               * ms-files rewriting manually, they don't need the extra directory, as they never
2325               * had wp-content/uploads for the main site.)
2326               */
2327  
2328              if ( defined( 'MULTISITE' ) ) {
2329                  $ms_dir = '/sites/' . get_current_blog_id();
2330              } else {
2331                  $ms_dir = '/' . get_current_blog_id();
2332              }
2333  
2334              $dir .= $ms_dir;
2335              $url .= $ms_dir;
2336  
2337          } elseif ( defined( 'UPLOADS' ) && ! ms_is_switched() ) {
2338              /*
2339               * Handle the old-form ms-files.php rewriting if the network still has that enabled.
2340               * When ms-files rewriting is enabled, then we only listen to UPLOADS when:
2341               * 1) We are not on the main site in a post-MU network, as wp-content/uploads is used
2342               *    there, and
2343               * 2) We are not switched, as ms_upload_constants() hardcodes these constants to reflect
2344               *    the original blog ID.
2345               *
2346               * Rather than UPLOADS, we actually use BLOGUPLOADDIR if it is set, as it is absolute.
2347               * (And it will be set, see ms_upload_constants().) Otherwise, UPLOADS can be used, as
2348               * as it is relative to ABSPATH. For the final piece: when UPLOADS is used with ms-files
2349               * rewriting in multisite, the resulting URL is /files. (#WP22702 for background.)
2350               */
2351  
2352              if ( defined( 'BLOGUPLOADDIR' ) ) {
2353                  $dir = untrailingslashit( BLOGUPLOADDIR );
2354              } else {
2355                  $dir = ABSPATH . UPLOADS;
2356              }
2357              $url = trailingslashit( $siteurl ) . 'files';
2358          }
2359      }
2360  
2361      $basedir = $dir;
2362      $baseurl = $url;
2363  
2364      $subdir = '';
2365      if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
2366          // Generate the yearly and monthly dirs
2367          if ( ! $time ) {
2368              $time = current_time( 'mysql' );
2369          }
2370          $y      = substr( $time, 0, 4 );
2371          $m      = substr( $time, 5, 2 );
2372          $subdir = "/$y/$m";
2373      }
2374  
2375      $dir .= $subdir;
2376      $url .= $subdir;
2377  
2378      return array(
2379          'path'    => $dir,
2380          'url'     => $url,
2381          'subdir'  => $subdir,
2382          'basedir' => $basedir,
2383          'baseurl' => $baseurl,
2384          'error'   => false,
2385      );
2386  }
2387  
2388  /**
2389   * Get a filename that is sanitized and unique for the given directory.
2390   *
2391   * If the filename is not unique, then a number will be added to the filename
2392   * before the extension, and will continue adding numbers until the filename is
2393   * unique.
2394   *
2395   * The callback is passed three parameters, the first one is the directory, the
2396   * second is the filename, and the third is the extension.
2397   *
2398   * @since 2.5.0
2399   *
2400   * @param string   $dir                      Directory.
2401   * @param string   $filename                 File name.
2402   * @param callable $unique_filename_callback Callback. Default null.
2403   * @return string New filename, if given wasn't unique.
2404   */
2405  function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) {
2406      // Sanitize the file name before we begin processing.
2407      $filename = sanitize_file_name( $filename );
2408  
2409      // Separate the filename into a name and extension.
2410      $ext  = pathinfo( $filename, PATHINFO_EXTENSION );
2411      $name = pathinfo( $filename, PATHINFO_BASENAME );
2412      if ( $ext ) {
2413          $ext = '.' . $ext;
2414      }
2415  
2416      // Edge case: if file is named '.ext', treat as an empty name.
2417      if ( $name === $ext ) {
2418          $name = '';
2419      }
2420  
2421      /*
2422       * Increment the file number until we have a unique file to save in $dir.
2423       * Use callback if supplied.
2424       */
2425      if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) {
2426          $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext );
2427      } else {
2428          $number = '';
2429  
2430          // Change '.ext' to lower case.
2431          if ( $ext && strtolower( $ext ) != $ext ) {
2432              $ext2      = strtolower( $ext );
2433              $filename2 = preg_replace( '|' . preg_quote( $ext ) . '$|', $ext2, $filename );
2434  
2435              // Check for both lower and upper case extension or image sub-sizes may be overwritten.
2436              while ( file_exists( $dir . "/$filename" ) || file_exists( $dir . "/$filename2" ) ) {
2437                  $new_number = (int) $number + 1;
2438                  $filename   = str_replace( array( "-$number$ext", "$number$ext" ), "-$new_number$ext", $filename );
2439                  $filename2  = str_replace( array( "-$number$ext2", "$number$ext2" ), "-$new_number$ext2", $filename2 );
2440                  $number     = $new_number;
2441              }
2442  
2443              /**
2444               * Filters the result when generating a unique file name.
2445               *
2446               * @since 4.5.0
2447               *
2448               * @param string        $filename                 Unique file name.
2449               * @param string        $ext                      File extension, eg. ".png".
2450               * @param string        $dir                      Directory path.
2451               * @param callable|null $unique_filename_callback Callback function that generates the unique file name.
2452               */
2453              return apply_filters( 'wp_unique_filename', $filename2, $ext, $dir, $unique_filename_callback );
2454          }
2455  
2456          while ( file_exists( $dir . "/$filename" ) ) {
2457              $new_number = (int) $number + 1;
2458              if ( '' == "$number$ext" ) {
2459                  $filename = "$filename-" . $new_number;
2460              } else {
2461                  $filename = str_replace( array( "-$number$ext", "$number$ext" ), '-' . $new_number . $ext, $filename );
2462              }
2463              $number = $new_number;
2464          }
2465      }
2466  
2467      /** This filter is documented in wp-includes/functions.php */
2468      return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback );
2469  }
2470  
2471  /**
2472   * Create a file in the upload folder with given content.
2473   *
2474   * If there is an error, then the key 'error' will exist with the error message.
2475   * If success, then the key 'file' will have the unique file path, the 'url' key
2476   * will have the link to the new file. and the 'error' key will be set to false.
2477   *
2478   * This function will not move an uploaded file to the upload folder. It will
2479   * create a new file with the content in $bits parameter. If you move the upload
2480   * file, read the content of the uploaded file, and then you can give the
2481   * filename and content to this function, which will add it to the upload
2482   * folder.
2483   *
2484   * The permissions will be set on the new file automatically by this function.
2485   *
2486   * @since 2.0.0
2487   *
2488   * @param string       $name       Filename.
2489   * @param null|string  $deprecated Never used. Set to null.
2490   * @param string       $bits       File content
2491   * @param string       $time       Optional. Time formatted in 'yyyy/mm'. Default null.
2492   * @return array
2493   */
2494  function wp_upload_bits( $name, $deprecated, $bits, $time = null ) {
2495      if ( ! empty( $deprecated ) ) {
2496          _deprecated_argument( __FUNCTION__, '2.0.0' );
2497      }
2498  
2499      if ( empty( $name ) ) {
2500          return array( 'error' => __( 'Empty filename' ) );
2501      }
2502  
2503      $wp_filetype = wp_check_filetype( $name );
2504      if ( ! $wp_filetype['ext'] && ! current_user_can( 'unfiltered_upload' ) ) {
2505          return array( 'error' => __( 'Sorry, this file type is not permitted for security reasons.' ) );
2506      }
2507  
2508      $upload = wp_upload_dir( $time );
2509  
2510      if ( $upload['error'] !== false ) {
2511          return $upload;
2512      }
2513  
2514      /**
2515       * Filters whether to treat the upload bits as an error.
2516       *
2517       * Passing a non-array to the filter will effectively short-circuit preparing
2518       * the upload bits, returning that value instead.
2519       *
2520       * @since 3.0.0
2521       *
2522       * @param mixed $upload_bits_error An array of upload bits data, or a non-array error to return.
2523       */
2524      $upload_bits_error = apply_filters(
2525          'wp_upload_bits',
2526          array(
2527              'name' => $name,
2528              'bits' => $bits,
2529              'time' => $time,
2530          )
2531      );
2532      if ( ! is_array( $upload_bits_error ) ) {
2533          $upload['error'] = $upload_bits_error;
2534          return $upload;
2535      }
2536  
2537      $filename = wp_unique_filename( $upload['path'], $name );
2538  
2539      $new_file = $upload['path'] . "/$filename";
2540      if ( ! wp_mkdir_p( dirname( $new_file ) ) ) {
2541          if ( 0 === strpos( $upload['basedir'], ABSPATH ) ) {
2542              $error_path = str_replace( ABSPATH, '', $upload['basedir'] ) . $upload['subdir'];
2543          } else {
2544              $error_path = wp_basename( $upload['basedir'] ) . $upload['subdir'];
2545          }
2546  
2547          $message = sprintf(
2548              /* translators: %s: Directory path. */
2549              __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
2550              $error_path
2551          );
2552          return array( 'error' => $message );
2553      }
2554  
2555      $ifp = @fopen( $new_file, 'wb' );
2556      if ( ! $ifp ) {
2557          return array(
2558              /* translators: %s: File name. */
2559              'error' => sprintf( __( 'Could not write file %s' ), $new_file ),
2560          );
2561      }
2562  
2563      fwrite( $ifp, $bits );
2564      fclose( $ifp );
2565      clearstatcache();
2566  
2567      // Set correct file permissions
2568      $stat  = @ stat( dirname( $new_file ) );
2569      $perms = $stat['mode'] & 0007777;
2570      $perms = $perms & 0000666;
2571      chmod( $new_file, $perms );
2572      clearstatcache();
2573  
2574      // Compute the URL
2575      $url = $upload['url'] . "/$filename";
2576  
2577      /** This filter is documented in wp-admin/includes/file.php */
2578      return apply_filters(
2579          'wp_handle_upload',
2580          array(
2581              'file'  => $new_file,
2582              'url'   => $url,
2583              'type'  => $wp_filetype['type'],
2584              'error' => false,
2585          ),
2586          'sideload'
2587      );
2588  }
2589  
2590  /**
2591   * Retrieve the file type based on the extension name.
2592   *
2593   * @since 2.5.0
2594   *
2595   * @param string $ext The extension to search.
2596   * @return string|void The file type, example: audio, video, document, spreadsheet, etc.
2597   */
2598  function wp_ext2type( $ext ) {
2599      $ext = strtolower( $ext );
2600  
2601      $ext2type = wp_get_ext_types();
2602      foreach ( $ext2type as $type => $exts ) {
2603          if ( in_array( $ext, $exts ) ) {
2604              return $type;
2605          }
2606      }
2607  }
2608  
2609  /**
2610   * Retrieve the file type from the file name.
2611   *
2612   * You can optionally define the mime array, if needed.
2613   *
2614   * @since 2.0.4
2615   *
2616   * @param string   $filename File name or path.
2617   * @param string[] $mimes    Optional. Array of mime types keyed by their file extension regex.
2618   * @return array {
2619   *     Values for the extension and mime type.
2620   *
2621   *     @type string|false $ext  File extension, or false if the file doesn't match a mime type.
2622   *     @type string|false $type File mime type, or false if the file doesn't match a mime type.
2623   * }
2624   */
2625  function wp_check_filetype( $filename, $mimes = null ) {
2626      if ( empty( $mimes ) ) {
2627          $mimes = get_allowed_mime_types();
2628      }
2629      $type = false;
2630      $ext  = false;
2631  
2632      foreach ( $mimes as $ext_preg => $mime_match ) {
2633          $ext_preg = '!\.(' . $ext_preg . ')$!i';
2634          if ( preg_match( $ext_preg, $filename, $ext_matches ) ) {
2635              $type = $mime_match;
2636              $ext  = $ext_matches[1];
2637              break;
2638          }
2639      }
2640  
2641      return compact( 'ext', 'type' );
2642  }
2643  
2644  /**
2645   * Attempt to determine the real file type of a file.
2646   *
2647   * If unable to, the file name extension will be used to determine type.
2648   *
2649   * If it's determined that the extension does not match the file's real type,
2650   * then the "proper_filename" value will be set with a proper filename and extension.
2651   *
2652   * Currently this function only supports renaming images validated via wp_get_image_mime().
2653   *
2654   * @since 3.0.0
2655   *
2656   * @param string   $file     Full path to the file.
2657   * @param string   $filename The name of the file (may differ from $file due to $file being
2658   *                           in a tmp directory).
2659   * @param string[] $mimes    Optional. Array of mime types keyed by their file extension regex.
2660   * @return array {
2661   *     Values for the extension, mime type, and corrected filename.
2662   *
2663   *     @type string|false $ext             File extension, or false if the file doesn't match a mime type.
2664   *     @type string|false $type            File mime type, or false if the file doesn't match a mime type.
2665   *     @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
2666   * }
2667   */
2668  function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
2669      $proper_filename = false;
2670  
2671      // Do basic extension validation and MIME mapping
2672      $wp_filetype = wp_check_filetype( $filename, $mimes );
2673      $ext         = $wp_filetype['ext'];
2674      $type        = $wp_filetype['type'];
2675  
2676      // We can't do any further validation without a file to work with
2677      if ( ! file_exists( $file ) ) {
2678          return compact( 'ext', 'type', 'proper_filename' );
2679      }
2680  
2681      $real_mime = false;
2682  
2683      // Validate image types.
2684      if ( $type && 0 === strpos( $type, 'image/' ) ) {
2685  
2686          // Attempt to figure out what type of image it actually is
2687          $real_mime = wp_get_image_mime( $file );
2688  
2689          if ( $real_mime && $real_mime != $type ) {
2690              /**
2691               * Filters the list mapping image mime types to their respective extensions.
2692               *
2693               * @since 3.0.0
2694               *
2695               * @param  array $mime_to_ext Array of image mime types and their matching extensions.
2696               */
2697              $mime_to_ext = apply_filters(
2698                  'getimagesize_mimes_to_exts',
2699                  array(
2700                      'image/jpeg' => 'jpg',
2701                      'image/png'  => 'png',
2702                      'image/gif'  => 'gif',
2703                      'image/bmp'  => 'bmp',
2704                      'image/tiff' => 'tif',
2705                  )
2706              );
2707  
2708              // Replace whatever is after the last period in the filename with the correct extension
2709              if ( ! empty( $mime_to_ext[ $real_mime ] ) ) {
2710                  $filename_parts = explode( '.', $filename );
2711                  array_pop( $filename_parts );
2712                  $filename_parts[] = $mime_to_ext[ $real_mime ];
2713                  $new_filename     = implode( '.', $filename_parts );
2714  
2715                  if ( $new_filename != $filename ) {
2716                      $proper_filename = $new_filename; // Mark that it changed
2717                  }
2718                  // Redefine the extension / MIME
2719                  $wp_filetype = wp_check_filetype( $new_filename, $mimes );
2720                  $ext         = $wp_filetype['ext'];
2721                  $type        = $wp_filetype['type'];
2722              } else {
2723                  // Reset $real_mime and try validating again.
2724                  $real_mime = false;
2725              }
2726          }
2727      }
2728  
2729      // Validate files that didn't get validated during previous checks.
2730      if ( $type && ! $real_mime && extension_loaded( 'fileinfo' ) ) {
2731          $finfo     = finfo_open( FILEINFO_MIME_TYPE );
2732          $real_mime = finfo_file( $finfo, $file );
2733          finfo_close( $finfo );
2734  
2735          // fileinfo often misidentifies obscure files as one of these types
2736          $nonspecific_types = array(
2737              'application/octet-stream',
2738              'application/encrypted',
2739              'application/CDFV2-encrypted',
2740              'application/zip',
2741          );
2742  
2743          /*
2744           * If $real_mime doesn't match the content type we're expecting from the file's extension,
2745           * we need to do some additional vetting. Media types and those listed in $nonspecific_types are
2746           * allowed some leeway, but anything else must exactly match the real content type.
2747           */
2748          if ( in_array( $real_mime, $nonspecific_types, true ) ) {
2749              // File is a non-specific binary type. That's ok if it's a type that generally tends to be binary.
2750              if ( ! in_array( substr( $type, 0, strcspn( $type, '/' ) ), array( 'application', 'video', 'audio' ) ) ) {
2751                  $type = false;
2752                  $ext  = false;
2753              }
2754          } elseif ( 0 === strpos( $real_mime, 'video/' ) || 0 === strpos( $real_mime, 'audio/' ) ) {
2755              /*
2756               * For these types, only the major type must match the real value.
2757               * This means that common mismatches are forgiven: application/vnd.apple.numbers is often misidentified as application/zip,
2758               * and some media files are commonly named with the wrong extension (.mov instead of .mp4)
2759               */
2760              if ( substr( $real_mime, 0, strcspn( $real_mime, '/' ) ) !== substr( $type, 0, strcspn( $type, '/' ) ) ) {
2761                  $type = false;
2762                  $ext  = false;
2763              }
2764          } elseif ( 'text/plain' === $real_mime ) {
2765              // A few common file types are occasionally detected as text/plain; allow those.
2766              if ( ! in_array(
2767                  $type,
2768                  array(
2769                      'text/plain',
2770                      'text/csv',
2771                      'text/richtext',
2772                      'text/tsv',
2773                      'text/vtt',
2774                  )
2775              )
2776              ) {
2777                  $type = false;
2778                  $ext  = false;
2779              }
2780          } elseif ( 'text/rtf' === $real_mime ) {
2781              // Special casing for RTF files.
2782              if ( ! in_array(
2783                  $type,
2784                  array(
2785                      'text/rtf',
2786                      'text/plain',
2787                      'application/rtf',
2788                  )
2789              )
2790              ) {
2791                  $type = false;
2792                  $ext  = false;
2793              }
2794          } else {
2795              if ( $type !== $real_mime ) {
2796                  /*
2797                   * Everything else including image/* and application/*:
2798                   * If the real content type doesn't match the file extension, assume it's dangerous.
2799                   */
2800                  $type = false;
2801                  $ext  = false;
2802              }
2803          }
2804      }
2805  
2806      // The mime type must be allowed
2807      if ( $type ) {
2808          $allowed = get_allowed_mime_types();
2809  
2810          if ( ! in_array( $type, $allowed ) ) {
2811              $type = false;
2812              $ext  = false;
2813          }
2814      }
2815  
2816      /**
2817       * Filters the "real" file type of the given file.
2818       *
2819       * @since 3.0.0
2820       * @since 5.1.0 The $real_mime parameter was added.
2821       *
2822       * @param array       $wp_check_filetype_and_ext {
2823       *     Values for the extension, mime type, and corrected filename.
2824       *
2825       *     @type string|false $ext             File extension, or false if the file doesn't match a mime type.
2826       *     @type string|false $type            File mime type, or false if the file doesn't match a mime type.
2827       *     @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
2828       * }
2829       * @param string      $file                      Full path to the file.
2830       * @param string      $filename                  The name of the file (may differ from $file due to
2831       *                                               $file being in a tmp directory).
2832       * @param string[]    $mimes                     Array of mime types keyed by their file extension regex.
2833       * @param string|bool $real_mime                 The actual mime type or false if the type cannot be determined.
2834       */
2835      return apply_filters( 'wp_check_filetype_and_ext', compact( 'ext', 'type', 'proper_filename' ), $file, $filename, $mimes, $real_mime );
2836  }
2837  
2838  /**
2839   * Returns the real mime type of an image file.
2840   *
2841   * This depends on exif_imagetype() or getimagesize() to determine real mime types.
2842   *
2843   * @since 4.7.1
2844   *
2845   * @param string $file Full path to the file.
2846   * @return string|false The actual mime type or false if the type cannot be determined.
2847   */
2848  function wp_get_image_mime( $file ) {
2849      /*
2850       * Use exif_imagetype() to check the mimetype if available or fall back to
2851       * getimagesize() if exif isn't avaialbe. If either function throws an Exception
2852       * we assume the file could not be validated.
2853       */
2854      try {
2855          if ( is_callable( 'exif_imagetype' ) ) {
2856              $imagetype = exif_imagetype( $file );
2857              $mime      = ( $imagetype ) ? image_type_to_mime_type( $imagetype ) : false;
2858          } elseif ( function_exists( 'getimagesize' ) ) {
2859              $imagesize = @getimagesize( $file );
2860              $mime      = ( isset( $imagesize['mime'] ) ) ? $imagesize['mime'] : false;
2861          } else {
2862              $mime = false;
2863          }
2864      } catch ( Exception $e ) {
2865          $mime = false;
2866      }
2867  
2868      return $mime;
2869  }
2870  
2871  /**
2872   * Retrieve list of mime types and file extensions.
2873   *
2874   * @since 3.5.0
2875   * @since 4.2.0 Support was added for GIMP (.xcf) files.
2876   * @since 4.9.2 Support was added for Flac (.flac) files.
2877   * @since 4.9.6 Support was added for AAC (.aac) files.
2878   *
2879   * @return string[] Array of mime types keyed by the file extension regex corresponding to those types.
2880   */
2881  function wp_get_mime_types() {
2882      /**
2883       * Filters the list of mime types and file extensions.
2884       *
2885       * This filter should be used to add, not remove, mime types. To remove
2886       * mime types, use the {@see 'upload_mimes'} filter.
2887       *
2888       * @since 3.5.0
2889       *
2890       * @param string[] $wp_get_mime_types Mime types keyed by the file extension regex
2891       *                                 corresponding to those types.
2892       */
2893      return apply_filters(
2894          'mime_types',
2895          array(
2896              // Image formats.
2897              'jpg|jpeg|jpe'                 => 'image/jpeg',
2898              'gif'                          => 'image/gif',
2899              'png'                          => 'image/png',
2900              'bmp'                          => 'image/bmp',
2901              'tiff|tif'                     => 'image/tiff',
2902              'ico'                          => 'image/x-icon',
2903              // Video formats.
2904              'asf|asx'                      => 'video/x-ms-asf',
2905              'wmv'                          => 'video/x-ms-wmv',
2906              'wmx'                          => 'video/x-ms-wmx',
2907              'wm'                           => 'video/x-ms-wm',
2908              'avi'                          => 'video/avi',
2909              'divx'                         => 'video/divx',
2910              'flv'                          => 'video/x-flv',
2911              'mov|qt'                       => 'video/quicktime',
2912              'mpeg|mpg|mpe'                 => 'video/mpeg',
2913              'mp4|m4v'                      => 'video/mp4',
2914              'ogv'                          => 'video/ogg',
2915              'webm'                         => 'video/webm',
2916              'mkv'                          => 'video/x-matroska',
2917              '3gp|3gpp'                     => 'video/3gpp', // Can also be audio
2918              '3g2|3gp2'                     => 'video/3gpp2', // Can also be audio
2919              // Text formats.
2920              'txt|asc|c|cc|h|srt'           => 'text/plain',
2921              'csv'                          => 'text/csv',
2922              'tsv'                          => 'text/tab-separated-values',
2923              'ics'                          => 'text/calendar',
2924              'rtx'                          => 'text/richtext',
2925              'css'                          => 'text/css',
2926              'htm|html'                     => 'text/html',
2927              'vtt'                          => 'text/vtt',
2928              'dfxp'                         => 'application/ttaf+xml',
2929              // Audio formats.
2930              'mp3|m4a|m4b'                  => 'audio/mpeg',
2931              'aac'                          => 'audio/aac',
2932              'ra|ram'                       => 'audio/x-realaudio',
2933              'wav'                          => 'audio/wav',
2934              'ogg|oga'                      => 'audio/ogg',
2935              'flac'                         => 'audio/flac',
2936              'mid|midi'                     => 'audio/midi',
2937              'wma'                          => 'audio/x-ms-wma',
2938              'wax'                          => 'audio/x-ms-wax',
2939              'mka'                          => 'audio/x-matroska',
2940              // Misc application formats.
2941              'rtf'                          => 'application/rtf',
2942              'js'                           => 'application/javascript',
2943              'pdf'                          => 'application/pdf',
2944              'swf'                          => 'application/x-shockwave-flash',
2945              'class'                        => 'application/java',
2946              'tar'                          => 'application/x-tar',
2947              'zip'                          => 'application/zip',
2948              'gz|gzip'                      => 'application/x-gzip',
2949              'rar'                          => 'application/rar',
2950              '7z'                           => 'application/x-7z-compressed',
2951              'exe'                          => 'application/x-msdownload',
2952              'psd'                          => 'application/octet-stream',
2953              'xcf'                          => 'application/octet-stream',
2954              // MS Office formats.
2955              'doc'                          => 'application/msword',
2956              'pot|pps|ppt'                  => 'application/vnd.ms-powerpoint',
2957              'wri'                          => 'application/vnd.ms-write',
2958              'xla|xls|xlt|xlw'              => 'application/vnd.ms-excel',
2959              'mdb'                          => 'application/vnd.ms-access',
2960              'mpp'                          => 'application/vnd.ms-project',
2961              'docx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2962              'docm'                         => 'application/vnd.ms-word.document.macroEnabled.12',
2963              'dotx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
2964              'dotm'                         => 'application/vnd.ms-word.template.macroEnabled.12',
2965              'xlsx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2966              'xlsm'                         => 'application/vnd.ms-excel.sheet.macroEnabled.12',
2967              'xlsb'                         => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
2968              'xltx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
2969              'xltm'                         => 'application/vnd.ms-excel.template.macroEnabled.12',
2970              'xlam'                         => 'application/vnd.ms-excel.addin.macroEnabled.12',
2971              'pptx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2972              'pptm'                         => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
2973              'ppsx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
2974              'ppsm'                         => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
2975              'potx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.template',
2976              'potm'                         => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
2977              'ppam'                         => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
2978              'sldx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
2979              'sldm'                         => 'application/vnd.ms-powerpoint.slide.macroEnabled.12',
2980              'onetoc|onetoc2|onetmp|onepkg' => 'application/onenote',
2981              'oxps'                         => 'application/oxps',
2982              'xps'                          => 'application/vnd.ms-xpsdocument',
2983              // OpenOffice formats.
2984              'odt'                          => 'application/vnd.oasis.opendocument.text',
2985              'odp'                          => 'application/vnd.oasis.opendocument.presentation',
2986              'ods'                          => 'application/vnd.oasis.opendocument.spreadsheet',
2987              'odg'                          => 'application/vnd.oasis.opendocument.graphics',
2988              'odc'                          => 'application/vnd.oasis.opendocument.chart',
2989              'odb'                          => 'application/vnd.oasis.opendocument.database',
2990              'odf'                          => 'application/vnd.oasis.opendocument.formula',
2991              // WordPerfect formats.
2992              'wp|wpd'                       => 'application/wordperfect',
2993              // iWork formats.
2994              'key'                          => 'application/vnd.apple.keynote',
2995              'numbers'                      => 'application/vnd.apple.numbers',
2996              'pages'                        => 'application/vnd.apple.pages',
2997          )
2998      );
2999  }
3000  
3001  /**
3002   * Retrieves the list of common file extensions and their types.
3003   *
3004   * @since 4.6.0
3005   *
3006   * @return array[] Multi-dimensional array of file extensions types keyed by the type of file.
3007   */
3008  function wp_get_ext_types() {
3009  
3010      /**
3011       * Filters file type based on the extension name.
3012       *
3013       * @since 2.5.0
3014       *
3015       * @see wp_ext2type()
3016       *
3017       * @param array[] $ext2type Multi-dimensional array of file extensions types keyed by the type of file.
3018       */
3019      return apply_filters(
3020          'ext2type',
3021          array(
3022              'image'       => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico' ),
3023              'audio'       => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ),
3024              'video'       => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ),
3025              'document'    => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ),
3026              'spreadsheet' => array( 'numbers', 'ods', 'xls', 'xlsx', 'xlsm', 'xlsb' ),
3027              'interactive' => array( 'swf', 'key', 'ppt', 'pptx', 'pptm', 'pps', 'ppsx', 'ppsm', 'sldx', 'sldm', 'odp' ),
3028              'text'        => array( 'asc', 'csv', 'tsv', 'txt' ),
3029              'archive'     => array( 'bz2', 'cab', 'dmg', 'gz', 'rar', 'sea', 'sit', 'sqx', 'tar', 'tgz', 'zip', '7z' ),
3030              'code'        => array( 'css', 'htm', 'html', 'php', 'js' ),
3031          )
3032      );
3033  }
3034  
3035  /**
3036   * Retrieve list of allowed mime types and file extensions.
3037   *
3038   * @since 2.8.6
3039   *
3040   * @param int|WP_User $user Optional. User to check. Defaults to current user.
3041   * @return string[] Array of mime types keyed by the file extension regex corresponding
3042   *                  to those types.
3043   */
3044  function get_allowed_mime_types( $user = null ) {
3045      $t = wp_get_mime_types();
3046  
3047      unset( $t['swf'], $t['exe'] );
3048      if ( function_exists( 'current_user_can' ) ) {
3049          $unfiltered = $user ? user_can( $user, 'unfiltered_html' ) : current_user_can( 'unfiltered_html' );
3050      }
3051  
3052      if ( empty( $unfiltered ) ) {
3053          unset( $t['htm|html'], $t['js'] );
3054      }
3055  
3056      /**
3057       * Filters list of allowed mime types and file extensions.
3058       *
3059       * @since 2.0.0
3060       *
3061       * @param array            $t    Mime types keyed by the file extension regex corresponding to those types.
3062       * @param int|WP_User|null $user User ID, User object or null if not provided (indicates current user).
3063       */
3064      return apply_filters( 'upload_mimes', $t, $user );
3065  }
3066  
3067  /**
3068   * Display "Are You Sure" message to confirm the action being taken.
3069   *
3070   * If the action has the nonce explain message, then it will be displayed
3071   * along with the "Are you sure?" message.
3072   *
3073   * @since 2.0.4
3074   *
3075   * @param string $action The nonce action.
3076   */
3077  function wp_nonce_ays( $action ) {
3078      if ( 'log-out' == $action ) {
3079          $html = sprintf(
3080              /* translators: %s: Site title. */
3081              __( 'You are attempting to log out of %s' ),
3082              get_bloginfo( 'name' )
3083          );
3084          $html       .= '</p><p>';
3085          $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
3086          $html       .= sprintf(
3087              /* translators: %s: Logout URL. */
3088              __( 'Do you really want to <a href="%s">log out</a>?' ),
3089              wp_logout_url( $redirect_to )
3090          );
3091      } else {
3092          $html = __( 'The link you followed has expired.' );
3093          if ( wp_get_referer() ) {
3094              $html .= '</p><p>';
3095              $html .= sprintf(
3096                  '<a href="%s">%s</a>',
3097                  esc_url( remove_query_arg( 'updated', wp_get_referer() ) ),
3098                  __( 'Please try again.' )
3099              );
3100          }
3101      }
3102  
3103      wp_die( $html, __( 'Something went wrong.' ), 403 );
3104  }
3105  
3106  /**
3107   * Kills WordPress execution and displays HTML page with an error message.
3108   *
3109   * This function complements the `die()` PHP function. The difference is that
3110   * HTML will be displayed to the user. It is recommended to use this function
3111   * only when the execution should not continue any further. It is not recommended
3112   * to call this function very often, and try to handle as many errors as possible
3113   * silently or more gracefully.
3114   *
3115   * As a shorthand, the desired HTTP response code may be passed as an integer to
3116   * the `$title` parameter (the default title would apply) or the `$args` parameter.
3117   *
3118   * @since 2.0.4
3119   * @since 4.1.0 The `$title` and `$args` parameters were changed to optionally accept
3120   *              an integer to be used as the response code.
3121   * @since 5.1.0 The `$link_url`, `$link_text`, and `$exit` arguments were added.
3122   * @since 5.3.0 The `$charset` argument was added.
3123   *
3124   * @global WP_Query $wp_query WordPress Query object.
3125   *
3126   * @param string|WP_Error  $message Optional. Error message. If this is a WP_Error object,
3127   *                                  and not an Ajax or XML-RPC request, the error's messages are used.
3128   *                                  Default empty.
3129   * @param string|int       $title   Optional. Error title. If `$message` is a `WP_Error` object,
3130   *                                  error data with the key 'title' may be used to specify the title.
3131   *                                  If `$title` is an integer, then it is treated as the response
3132   *                                  code. Default empty.
3133   * @param string|array|int $args {
3134   *     Optional. Arguments to control behavior. If `$args` is an integer, then it is treated
3135   *     as the response code. Default empty array.
3136   *
3137   *     @type int    $response       The HTTP response code. Default 200 for Ajax requests, 500 otherwise.
3138   *     @type string $link_url       A URL to include a link to. Only works in combination with $link_text.
3139   *                                  Default empty string.
3140   *     @type string $link_text      A label for the link to include. Only works in combination with $link_url.
3141   *                                  Default empty string.
3142   *     @type bool   $back_link      Whether to include a link to go back. Default false.
3143   *     @type string $text_direction The text direction. This is only useful internally, when WordPress
3144   *                                  is still loading and the site's locale is not set up yet. Accepts 'rtl'.
3145   *                                  Default is the value of is_rtl().
3146   *     @type string $charset        Character set of the HTML output. Default 'utf-8'.
3147   *     @type string $code           Error code to use. Default is 'wp_die', or the main error code if $message
3148   *                                  is a WP_Error.
3149   *     @type bool   $exit           Whether to exit the process after completion. Default true.
3150   * }
3151   */
3152  function wp_die( $message = '', $title = '', $args = array() ) {
3153      global $wp_query;
3154  
3155      if ( is_int( $args ) ) {
3156          $args = array( 'response' => $args );
3157      } elseif ( is_int( $title ) ) {
3158          $args  = array( 'response' => $title );
3159          $title = '';
3160      }
3161  
3162      if ( wp_doing_ajax() ) {
3163          /**
3164           * Filters the callback for killing WordPress execution for Ajax requests.
3165           *
3166           * @since 3.4.0
3167           *
3168           * @param callable $function Callback function name.
3169           */
3170          $function = apply_filters( 'wp_die_ajax_handler', '_ajax_wp_die_handler' );
3171      } elseif ( wp_is_json_request() ) {
3172          /**
3173           * Filters the callback for killing WordPress execution for JSON requests.
3174           *
3175           * @since 5.1.0
3176           *
3177           * @param callable $function Callback function name.
3178           */
3179          $function = apply_filters( 'wp_die_json_handler', '_json_wp_die_handler' );
3180      } elseif ( wp_is_jsonp_request() ) {
3181          /**
3182           * Filters the callback for killing WordPress execution for JSONP requests.
3183           *
3184           * @since 5.2.0
3185           *
3186           * @param callable $function Callback function name.
3187           */
3188          $function = apply_filters( 'wp_die_jsonp_handler', '_jsonp_wp_die_handler' );
3189      } elseif ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
3190          /**
3191           * Filters the callback for killing WordPress execution for XML-RPC requests.
3192           *
3193           * @since 3.4.0
3194           *
3195           * @param callable $function Callback function name.
3196           */
3197          $function = apply_filters( 'wp_die_xmlrpc_handler', '_xmlrpc_wp_die_handler' );
3198      } elseif ( wp_is_xml_request()
3199          || isset( $wp_query ) &&
3200              ( function_exists( 'is_feed' ) && is_feed()
3201              || function_exists( 'is_comment_feed' ) && is_comment_feed()
3202              || function_exists( 'is_trackback' ) && is_trackback() ) ) {
3203          /**
3204           * Filters the callback for killing WordPress execution for XML requests.
3205           *
3206           * @since 5.2.0
3207           *
3208           * @param callable $function Callback function name.
3209           */
3210          $function = apply_filters( 'wp_die_xml_handler', '_xml_wp_die_handler' );
3211      } else {
3212          /**
3213           * Filters the callback for killing WordPress execution for all non-Ajax, non-JSON, non-XML requests.
3214           *
3215           * @since 3.0.0
3216           *
3217           * @param callable $function Callback function name.
3218           */
3219          $function = apply_filters( 'wp_die_handler', '_default_wp_die_handler' );
3220      }
3221  
3222      call_user_func( $function, $message, $title, $args );
3223  }
3224  
3225  /**
3226   * Kills WordPress execution and displays HTML page with an error message.
3227   *
3228   * This is the default handler for wp_die(). If you want a custom one,
3229   * you can override this using the {@see 'wp_die_handler'} filter in wp_die().
3230   *
3231   * @since 3.0.0
3232   * @access private
3233   *
3234   * @param string|WP_Error $message Error message or WP_Error object.
3235   * @param string          $title   Optional. Error title. Default empty.
3236   * @param string|array    $args    Optional. Arguments to control behavior. Default empty array.
3237   */
3238  function _default_wp_die_handler( $message, $title = '', $args = array() ) {
3239      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3240  
3241      if ( is_string( $message ) ) {
3242          if ( ! empty( $parsed_args['additional_errors'] ) ) {
3243              $message = array_merge(
3244                  array( $message ),
3245                  wp_list_pluck( $parsed_args['additional_errors'], 'message' )
3246              );
3247              $message = "<ul>\n\t\t<li>" . join( "</li>\n\t\t<li>", $message ) . "</li>\n\t</ul>";
3248          }
3249  
3250          $message = sprintf(
3251              '<div class="wp-die-message">%s</div>',
3252              $message
3253          );
3254      }
3255  
3256      $have_gettext = function_exists( '__' );
3257  
3258      if ( ! empty( $parsed_args['link_url'] ) && ! empty( $parsed_args['link_text'] ) ) {
3259          $link_url = $parsed_args['link_url'];
3260          if ( function_exists( 'esc_url' ) ) {
3261              $link_url = esc_url( $link_url );
3262          }
3263          $link_text = $parsed_args['link_text'];
3264          $message  .= "\n<p><a href='{$link_url}'>{$link_text}</a></p>";
3265      }
3266  
3267      if ( isset( $parsed_args['back_link'] ) && $parsed_args['back_link'] ) {
3268          $back_text = $have_gettext ? __( '&laquo; Back' ) : '&laquo; Back';
3269          $message  .= "\n<p><a href='javascript:history.back()'>$back_text</a></p>";
3270      }
3271  
3272      if ( ! did_action( 'admin_head' ) ) :
3273          if ( ! headers_sent() ) {
3274              header( "Content-Type: text/html; charset={$parsed_args['charset']}" );
3275              status_header( $parsed_args['response'] );
3276              nocache_headers();
3277          }
3278  
3279          $text_direction = $parsed_args['text_direction'];
3280          if ( function_exists( 'language_attributes' ) && function_exists( 'is_rtl' ) ) {
3281              $dir_attr = get_language_attributes();
3282          } else {
3283              $dir_attr = "dir='$text_direction'";
3284          }
3285          ?>
3286  <!DOCTYPE html>
3287  <html xmlns="http://www.w3.org/1999/xhtml" <?php echo $dir_attr; ?>>
3288  <head>
3289      <meta http-equiv="Content-Type" content="text/html; charset=<?php echo $parsed_args['charset']; ?>" />
3290      <meta name="viewport" content="width=device-width">
3291          <?php
3292          if ( function_exists( 'wp_no_robots' ) ) {
3293              wp_no_robots();
3294          }
3295          ?>
3296      <title><?php echo $title; ?></title>
3297      <style type="text/css">
3298          html {
3299              background: #f1f1f1;
3300          }
3301          body {
3302              background: #fff;
3303              color: #444;
3304              font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
3305              margin: 2em auto;
3306              padding: 1em 2em;
3307              max-width: 700px;
3308              -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
3309              box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
3310          }
3311          h1 {
3312              border-bottom: 1px solid #dadada;
3313              clear: both;
3314              color: #666;
3315              font-size: 24px;
3316              margin: 30px 0 0 0;
3317              padding: 0;
3318              padding-bottom: 7px;
3319          }
3320          #error-page {
3321              margin-top: 50px;
3322          }
3323          #error-page p,
3324          #error-page .wp-die-message {
3325              font-size: 14px;
3326              line-height: 1.5;
3327              margin: 25px 0 20px;
3328          }
3329          #error-page code {
3330              font-family: Consolas, Monaco, monospace;
3331          }
3332          ul li {
3333              margin-bottom: 10px;
3334              font-size: 14px ;
3335          }
3336          a {
3337              color: #0073aa;
3338          }
3339          a:hover,
3340          a:active {
3341              color: #00a0d2;
3342          }
3343          a:focus {
3344              color: #124964;
3345              -webkit-box-shadow:
3346                  0 0 0 1px #5b9dd9,
3347                  0 0 2px 1px rgba(30, 140, 190, 0.8);
3348              box-shadow:
3349                  0 0 0 1px #5b9dd9,
3350                  0 0 2px 1px rgba(30, 140, 190, 0.8);
3351              outline: none;
3352          }
3353          .button {
3354              background: #f7f7f7;
3355              border: 1px solid #ccc;
3356              color: #555;
3357              display: inline-block;
3358              text-decoration: none;
3359              font-size: 13px;
3360              line-height: 2;
3361              height: 28px;
3362              margin: 0;
3363              padding: 0 10px 1px;
3364              cursor: pointer;
3365              -webkit-border-radius: 3px;
3366              -webkit-appearance: none;
3367              border-radius: 3px;
3368              white-space: nowrap;
3369              -webkit-box-sizing: border-box;
3370              -moz-box-sizing:    border-box;
3371              box-sizing:         border-box;
3372  
3373              -webkit-box-shadow: 0 1px 0 #ccc;
3374              box-shadow: 0 1px 0 #ccc;
3375              vertical-align: top;
3376          }
3377  
3378          .button.button-large {
3379              height: 30px;
3380              line-height: 2.15384615;
3381              padding: 0 12px 2px;
3382          }
3383  
3384          .button:hover,
3385          .button:focus {
3386              background: #fafafa;
3387              border-color: #999;
3388              color: #23282d;
3389          }
3390  
3391          .button:focus {
3392              border-color: #5b9dd9;
3393              -webkit-box-shadow: 0 0 3px rgba(0, 115, 170, 0.8);
3394              box-shadow: 0 0 3px rgba(0, 115, 170, 0.8);
3395              outline: none;
3396          }
3397  
3398          .button:active {
3399              background: #eee;
3400              border-color: #999;
3401              -webkit-box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
3402              box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
3403          }
3404  
3405          <?php
3406          if ( 'rtl' == $text_direction ) {
3407              echo 'body { font-family: Tahoma, Arial; }';
3408          }
3409          ?>
3410      </style>
3411  </head>
3412  <body id="error-page">
3413  <?php endif; // ! did_action( 'admin_head' ) ?>
3414      <?php echo $message; ?>
3415  </body>
3416  </html>
3417      <?php
3418      if ( $parsed_args['exit'] ) {
3419          die();
3420      }
3421  }
3422  
3423  /**
3424   * Kills WordPress execution and displays Ajax response with an error message.
3425   *
3426   * This is the handler for wp_die() when processing Ajax requests.
3427   *
3428   * @since 3.4.0
3429   * @access private
3430   *
3431   * @param string       $message Error message.
3432   * @param string       $title   Optional. Error title (unused). Default empty.
3433   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3434   */
3435  function _ajax_wp_die_handler( $message, $title = '', $args = array() ) {
3436      // Set default 'response' to 200 for AJAX requests.
3437      $args = wp_parse_args(
3438          $args,
3439          array( 'response' => 200 )
3440      );
3441  
3442      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3443  
3444      if ( ! headers_sent() ) {
3445          // This is intentional. For backward-compatibility, support passing null here.
3446          if ( null !== $args['response'] ) {
3447              status_header( $parsed_args['response'] );
3448          }
3449          nocache_headers();
3450      }
3451  
3452      if ( is_scalar( $message ) ) {
3453          $message = (string) $message;
3454      } else {
3455          $message = '0';
3456      }
3457  
3458      if ( $parsed_args['exit'] ) {
3459          die( $message );
3460      }
3461  
3462      echo $message;
3463  }
3464  
3465  /**
3466   * Kills WordPress execution and displays JSON response with an error message.
3467   *
3468   * This is the handler for wp_die() when processing JSON requests.
3469   *
3470   * @since 5.1.0
3471   * @access private
3472   *
3473   * @param string       $message Error message.
3474   * @param string       $title   Optional. Error title. Default empty.
3475   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3476   */
3477  function _json_wp_die_handler( $message, $title = '', $args = array() ) {
3478      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3479  
3480      $data = array(
3481          'code'              => $parsed_args['code'],
3482          'message'           => $message,
3483          'data'              => array(
3484              'status' => $parsed_args['response'],
3485          ),
3486          'additional_errors' => $parsed_args['additional_errors'],
3487      );
3488  
3489      if ( ! headers_sent() ) {
3490          header( "Content-Type: application/json; charset={$parsed_args['charset']}" );
3491          if ( null !== $parsed_args['response'] ) {
3492              status_header( $parsed_args['response'] );
3493          }
3494          nocache_headers();
3495      }
3496  
3497      echo wp_json_encode( $data );
3498      if ( $parsed_args['exit'] ) {
3499          die();
3500      }
3501  }
3502  
3503  /**
3504   * Kills WordPress execution and displays JSONP response with an error message.
3505   *
3506   * This is the handler for wp_die() when processing JSONP requests.
3507   *
3508   * @since 5.2.0
3509   * @access private
3510   *
3511   * @param string       $message Error message.
3512   * @param string       $title   Optional. Error title. Default empty.
3513   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3514   */
3515  function _jsonp_wp_die_handler( $message, $title = '', $args = array() ) {
3516      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3517  
3518      $data = array(
3519          'code'              => $parsed_args['code'],
3520          'message'           => $message,
3521          'data'              => array(
3522              'status' => $parsed_args['response'],
3523          ),
3524          'additional_errors' => $parsed_args['additional_errors'],
3525      );
3526  
3527      if ( ! headers_sent() ) {
3528          header( "Content-Type: application/javascript; charset={$parsed_args['charset']}" );
3529          header( 'X-Content-Type-Options: nosniff' );
3530          header( 'X-Robots-Tag: noindex' );
3531          if ( null !== $parsed_args['response'] ) {
3532              status_header( $parsed_args['response'] );
3533          }
3534          nocache_headers();
3535      }
3536  
3537      $result         = wp_json_encode( $data );
3538      $jsonp_callback = $_GET['_jsonp'];
3539      echo '/**/' . $jsonp_callback . '(' . $result . ')';
3540      if ( $parsed_args['exit'] ) {
3541          die();
3542      }
3543  }
3544  
3545  /**
3546   * Kills WordPress execution and displays XML response with an error message.
3547   *
3548   * This is the handler for wp_die() when processing XMLRPC requests.
3549   *
3550   * @since 3.2.0
3551   * @access private
3552   *
3553   * @global wp_xmlrpc_server $wp_xmlrpc_server
3554   *
3555   * @param string       $message Error message.
3556   * @param string       $title   Optional. Error title. Default empty.
3557   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3558   */
3559  function _xmlrpc_wp_die_handler( $message, $title = '', $args = array() ) {
3560      global $wp_xmlrpc_server;
3561  
3562      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3563  
3564      if ( ! headers_sent() ) {
3565          nocache_headers();
3566      }
3567  
3568      if ( $wp_xmlrpc_server ) {
3569          $error = new IXR_Error( $parsed_args['response'], $message );
3570          $wp_xmlrpc_server->output( $error->getXml() );
3571      }
3572      if ( $parsed_args['exit'] ) {
3573          die();
3574      }
3575  }
3576  
3577  /**
3578   * Kills WordPress execution and displays XML response with an error message.
3579   *
3580   * This is the handler for wp_die() when processing XML requests.
3581   *
3582   * @since 5.2.0
3583   * @access private
3584   *
3585   * @param string       $message Error message.
3586   * @param string       $title   Optional. Error title. Default empty.
3587   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3588   */
3589  function _xml_wp_die_handler( $message, $title = '', $args = array() ) {
3590      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3591  
3592      $message = htmlspecialchars( $message );
3593      $title   = htmlspecialchars( $title );
3594  
3595      $xml = <<<EOD
3596  <error>
3597      <code>{$parsed_args['code']}</code>
3598      <title><![CDATA[{$title}]]></title>
3599      <message><![CDATA[{$message}]]></message>
3600      <data>
3601          <status>{$parsed_args['response']}</status>
3602      </data>
3603  </error>
3604  
3605  EOD;
3606  
3607      if ( ! headers_sent() ) {
3608          header( "Content-Type: text/xml; charset={$parsed_args['charset']}" );
3609          if ( null !== $parsed_args['response'] ) {
3610              status_header( $parsed_args['response'] );
3611          }
3612          nocache_headers();
3613      }
3614  
3615      echo $xml;
3616      if ( $parsed_args['exit'] ) {
3617          die();
3618      }
3619  }
3620  
3621  /**
3622   * Kills WordPress execution and displays an error message.
3623   *
3624   * This is the handler for wp_die() when processing APP requests.
3625   *
3626   * @since 3.4.0
3627   * @since 5.1.0 Added the $title and $args parameters.
3628   * @access private
3629   *
3630   * @param string       $message Optional. Response to print. Default empty.
3631   * @param string       $title   Optional. Error title (unused). Default empty.
3632   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3633   */
3634  function _scalar_wp_die_handler( $message = '', $title = '', $args = array() ) {
3635      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3636  
3637      if ( $parsed_args['exit'] ) {
3638          if ( is_scalar( $message ) ) {
3639              die( (string) $message );
3640          }
3641          die();
3642      }
3643  
3644      if ( is_scalar( $message ) ) {
3645          echo (string) $message;
3646      }
3647  }
3648  
3649  /**
3650   * Processes arguments passed to wp_die() consistently for its handlers.
3651   *
3652   * @since 5.1.0
3653   * @access private
3654   *
3655   * @param string|WP_Error $message Error message or WP_Error object.
3656   * @param string          $title   Optional. Error title. Default empty.
3657   * @param string|array    $args    Optional. Arguments to control behavior. Default empty array.
3658   * @return array {
3659   *     Processed arguments.
3660   *
3661   *     @type string $0 Error message.
3662   *     @type string $1 Error title.
3663   *     @type array  $2 Arguments to control behavior.
3664   * }
3665   */
3666  function _wp_die_process_input( $message, $title = '', $args = array() ) {
3667      $defaults = array(
3668          'response'          => 0,
3669          'code'              => '',
3670          'exit'              => true,
3671          'back_link'         => false,
3672          'link_url'          => '',
3673          'link_text'         => '',
3674          'text_direction'    => '',
3675          'charset'           => 'utf-8',
3676          'additional_errors' => array(),
3677      );
3678  
3679      $args = wp_parse_args( $args, $defaults );
3680  
3681      if ( function_exists( 'is_wp_error' ) && is_wp_error( $message ) ) {
3682          if ( ! empty( $message->errors ) ) {
3683              $errors = array();
3684              foreach ( (array) $message->errors as $error_code => $error_messages ) {
3685                  foreach ( (array) $error_messages as $error_message ) {
3686                      $errors[] = array(
3687                          'code'    => $error_code,
3688                          'message' => $error_message,
3689                          'data'    => $message->get_error_data( $error_code ),
3690                      );
3691                  }
3692              }
3693  
3694              $message = $errors[0]['message'];
3695              if ( empty( $args['code'] ) ) {
3696                  $args['code'] = $errors[0]['code'];
3697              }
3698              if ( empty( $args['response'] ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['status'] ) ) {
3699                  $args['response'] = $errors[0]['data']['status'];
3700              }
3701              if ( empty( $title ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['title'] ) ) {
3702                  $title = $errors[0]['data']['title'];
3703              }
3704  
3705              unset( $errors[0] );
3706              $args['additional_errors'] = array_values( $errors );
3707          } else {
3708              $message = '';
3709          }
3710      }
3711  
3712      $have_gettext = function_exists( '__' );
3713  
3714      // The $title and these specific $args must always have a non-empty value.
3715      if ( empty( $args['code'] ) ) {
3716          $args['code'] = 'wp_die';
3717      }
3718      if ( empty( $args['response'] ) ) {
3719          $args['response'] = 500;
3720      }
3721      if ( empty( $title ) ) {
3722          $title = $have_gettext ? __( 'WordPress &rsaquo; Error' ) : 'WordPress &rsaquo; Error';
3723      }
3724      if ( empty( $args['text_direction'] ) || ! in_array( $args['text_direction'], array( 'ltr', 'rtl' ), true ) ) {
3725          $args['text_direction'] = 'ltr';
3726          if ( function_exists( 'is_rtl' ) && is_rtl() ) {
3727              $args['text_direction'] = 'rtl';
3728          }
3729      }
3730  
3731      if ( ! empty( $args['charset'] ) ) {
3732          $args['charset'] = _canonical_charset( $args['charset'] );
3733      }
3734  
3735      return array( $message, $title, $args );
3736  }
3737  
3738  /**
3739   * Encode a variable into JSON, with some sanity checks.
3740   *
3741   * @since 4.1.0
3742   * @since 5.3.0 No longer handles support for PHP < 5.6.
3743   *
3744   * @param mixed $data    Variable (usually an array or object) to encode as JSON.
3745   * @param int   $options Optional. Options to be passed to json_encode(). Default 0.
3746   * @param int   $depth   Optional. Maximum depth to walk through $data. Must be
3747   *                       greater than 0. Default 512.
3748   * @return string|false The JSON encoded string, or false if it cannot be encoded.
3749   */
3750  function wp_json_encode( $data, $options = 0, $depth = 512 ) {
3751      $json = json_encode( $data, $options, $depth );
3752  
3753      // If json_encode() was successful, no need to do more sanity checking.
3754      if ( false !== $json ) {
3755          return $json;
3756      }
3757  
3758      try {
3759          $data = _wp_json_sanity_check( $data, $depth );
3760      } catch ( Exception $e ) {
3761          return false;
3762      }
3763  
3764      return json_encode( $data, $options, $depth );
3765  }
3766  
3767  /**
3768   * Perform sanity checks on data that shall be encoded to JSON.
3769   *
3770   * @ignore
3771   * @since 4.1.0
3772   * @access private
3773   *
3774   * @see wp_json_encode()
3775   *
3776   * @param mixed $data  Variable (usually an array or object) to encode as JSON.
3777   * @param int   $depth Maximum depth to walk through $data. Must be greater than 0.
3778   * @return mixed The sanitized data that shall be encoded to JSON.
3779   */
3780  function _wp_json_sanity_check( $data, $depth ) {
3781      if ( $depth < 0 ) {
3782          throw new Exception( 'Reached depth limit' );
3783      }
3784  
3785      if ( is_array( $data ) ) {
3786          $output = array();
3787          foreach ( $data as $id => $el ) {
3788              // Don't forget to sanitize the ID!
3789              if ( is_string( $id ) ) {
3790                  $clean_id = _wp_json_convert_string( $id );
3791              } else {
3792                  $clean_id = $id;
3793              }
3794  
3795              // Check the element type, so that we're only recursing if we really have to.
3796              if ( is_array( $el ) || is_object( $el ) ) {
3797                  $output[ $clean_id ] = _wp_json_sanity_check( $el, $depth - 1 );
3798              } elseif ( is_string( $el ) ) {
3799                  $output[ $clean_id ] = _wp_json_convert_string( $el );
3800              } else {
3801                  $output[ $clean_id ] = $el;
3802              }
3803          }
3804      } elseif ( is_object( $data ) ) {
3805          $output = new stdClass;
3806          foreach ( $data as $id => $el ) {
3807              if ( is_string( $id ) ) {
3808                  $clean_id = _wp_json_convert_string( $id );
3809              } else {
3810                  $clean_id = $id;
3811              }
3812  
3813              if ( is_array( $el ) || is_object( $el ) ) {
3814                  $output->$clean_id = _wp_json_sanity_check( $el, $depth - 1 );
3815              } elseif ( is_string( $el ) ) {
3816                  $output->$clean_id = _wp_json_convert_string( $el );
3817              } else {
3818                  $output->$clean_id = $el;
3819              }
3820          }
3821      } elseif ( is_string( $data ) ) {
3822          return _wp_json_convert_string( $data );
3823      } else {
3824          return $data;
3825      }
3826  
3827      return $output;
3828  }
3829  
3830  /**
3831   * Convert a string to UTF-8, so that it can be safely encoded to JSON.
3832   *
3833   * @ignore
3834   * @since 4.1.0
3835   * @access private
3836   *
3837   * @see _wp_json_sanity_check()
3838   *
3839   * @staticvar bool $use_mb
3840   *
3841   * @param string $string The string which is to be converted.
3842   * @return string The checked string.
3843   */
3844  function _wp_json_convert_string( $string ) {
3845      static $use_mb = null;
3846      if ( is_null( $use_mb ) ) {
3847          $use_mb = function_exists( 'mb_convert_encoding' );
3848      }
3849  
3850      if ( $use_mb ) {
3851          $encoding = mb_detect_encoding( $string, mb_detect_order(), true );
3852          if ( $encoding ) {
3853              return mb_convert_encoding( $string, 'UTF-8', $encoding );
3854          } else {
3855              return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
3856          }
3857      } else {
3858          return wp_check_invalid_utf8( $string, true );
3859      }
3860  }
3861  
3862  /**
3863   * Prepares response data to be serialized to JSON.
3864   *
3865   * This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
3866   *
3867   * @ignore
3868   * @since      4.4.0
3869   * @deprecated 5.3.0 This function is no longer needed as support for PHP 5.2-5.3
3870   *                   has been dropped.
3871   * @access     private
3872   *
3873   * @param mixed $data Native representation.
3874   * @return bool|int|float|null|string|array Data ready for `json_encode()`.
3875   */
3876  function _wp_json_prepare_data( $data ) {
3877      _deprecated_function( __FUNCTION__, '5.3.0' );
3878      return $data;
3879  }
3880  
3881  /**
3882   * Send a JSON response back to an Ajax request.
3883   *
3884   * @since 3.5.0
3885   * @since 4.7.0 The `$status_code` parameter was added.
3886   *
3887   * @param mixed $response    Variable (usually an array or object) to encode as JSON,
3888   *                           then print and die.
3889   * @param int   $status_code The HTTP status code to output.
3890   */
3891  function wp_send_json( $response, $status_code = null ) {
3892      if ( ! headers_sent() ) {
3893          header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
3894          if ( null !== $status_code ) {
3895              status_header( $status_code );
3896          }
3897      }
3898  
3899      echo wp_json_encode( $response );
3900  
3901      if ( wp_doing_ajax() ) {
3902          wp_die(
3903              '',
3904              '',
3905              array(
3906                  'response' => null,
3907              )
3908          );
3909      } else {
3910          die;
3911      }
3912  }
3913  
3914  /**
3915   * Send a JSON response back to an Ajax request, indicating success.
3916   *
3917   * @since 3.5.0
3918   * @since 4.7.0 The `$status_code` parameter was added.
3919   *
3920   * @param mixed $data        Data to encode as JSON, then print and die.
3921   * @param int   $status_code The HTTP status code to output.
3922   */
3923  function wp_send_json_success( $data = null, $status_code = null ) {
3924      $response = array( 'success' => true );
3925  
3926      if ( isset( $data ) ) {
3927          $response['data'] = $data;
3928      }
3929  
3930      wp_send_json( $response, $status_code );
3931  }
3932  
3933  /**
3934   * Send a JSON response back to an Ajax request, indicating failure.
3935   *
3936   * If the `$data` parameter is a WP_Error object, the errors
3937   * within the object are processed and output as an array of error
3938   * codes and corresponding messages. All other types are output
3939   * without further processing.
3940   *
3941   * @since 3.5.0
3942   * @since 4.1.0 The `$data` parameter is now processed if a WP_Error object is passed in.
3943   * @since 4.7.0 The `$status_code` parameter was added.
3944   *
3945   * @param mixed $data        Data to encode as JSON, then print and die.
3946   * @param int   $status_code The HTTP status code to output.
3947   */
3948  function wp_send_json_error( $data = null, $status_code = null ) {
3949      $response = array( 'success' => false );
3950  
3951      if ( isset( $data ) ) {
3952          if ( is_wp_error( $data ) ) {
3953              $result = array();
3954              foreach ( $data->errors as $code => $messages ) {
3955                  foreach ( $messages as $message ) {
3956                      $result[] = array(
3957                          'code'    => $code,
3958                          'message' => $message,
3959                      );
3960                  }
3961              }
3962  
3963              $response['data'] = $result;
3964          } else {
3965              $response['data'] = $data;
3966          }
3967      }
3968  
3969      wp_send_json( $response, $status_code );
3970  }
3971  
3972  /**
3973   * Checks that a JSONP callback is a valid JavaScript callback.
3974   *
3975   * Only allows alphanumeric characters and the dot character in callback
3976   * function names. This helps to mitigate XSS attacks caused by directly
3977   * outputting user input.
3978   *
3979   * @since 4.6.0
3980   *
3981   * @param string $callback Supplied JSONP callback function.
3982   * @return bool True if valid callback, otherwise false.
3983   */
3984  function wp_check_jsonp_callback( $callback ) {
3985      if ( ! is_string( $callback ) ) {
3986          return false;
3987      }
3988  
3989      preg_replace( '/[^\w\.]/', '', $callback, -1, $illegal_char_count );
3990  
3991      return 0 === $illegal_char_count;
3992  }
3993  
3994  /**
3995   * Retrieve the WordPress home page URL.
3996   *
3997   * If the constant named 'WP_HOME' exists, then it will be used and returned
3998   * by the function. This can be used to counter the redirection on your local
3999   * development environment.
4000   *
4001   * @since 2.2.0
4002   * @access private
4003   *
4004   * @see WP_HOME
4005   *
4006   * @param string $url URL for the home location.
4007   * @return string Homepage location.
4008   */
4009  function _config_wp_home( $url = '' ) {
4010      if ( defined( 'WP_HOME' ) ) {
4011          return untrailingslashit( WP_HOME );
4012      }
4013      return $url;
4014  }
4015  
4016  /**
4017   * Retrieve the WordPress site URL.
4018   *
4019   * If the constant named 'WP_SITEURL' is defined, then the value in that
4020   * constant will always be returned. This can be used for debugging a site
4021   * on your localhost while not having to change the database to your URL.
4022   *
4023   * @since 2.2.0
4024   * @access private
4025   *
4026   * @see WP_SITEURL
4027   *
4028   * @param string $url URL to set the WordPress site location.
4029   * @return string The WordPress Site URL.
4030   */
4031  function _config_wp_siteurl( $url = '' ) {
4032      if ( defined( 'WP_SITEURL' ) ) {
4033          return untrailingslashit( WP_SITEURL );
4034      }
4035      return $url;
4036  }
4037  
4038  /**
4039   * Delete the fresh site option.
4040   *
4041   * @since 4.7.0
4042   * @access private
4043   */
4044  function _delete_option_fresh_site() {
4045      update_option( 'fresh_site', '0' );
4046  }
4047  
4048  /**
4049   * Set the localized direction for MCE plugin.
4050   *
4051   * Will only set the direction to 'rtl', if the WordPress locale has
4052   * the text direction set to 'rtl'.
4053   *
4054   * Fills in the 'directionality' setting, enables the 'directionality'
4055   * plugin, and adds the 'ltr' button to 'toolbar1', formerly
4056   * 'theme_advanced_buttons1' array keys. These keys are then returned
4057   * in the $mce_init (TinyMCE settings) array.
4058   *
4059   * @since 2.1.0
4060   * @access private
4061   *
4062   * @param array $mce_init MCE settings array.
4063   * @return array Direction set for 'rtl', if needed by locale.
4064   */
4065  function _mce_set_direction( $mce_init ) {
4066      if ( is_rtl() ) {
4067          $mce_init['directionality'] = 'rtl';
4068          $mce_init['rtl_ui']         = true;
4069  
4070          if ( ! empty( $mce_init['plugins'] ) && strpos( $mce_init['plugins'], 'directionality' ) === false ) {
4071              $mce_init['plugins'] .= ',directionality';
4072          }
4073  
4074          if ( ! empty( $mce_init['toolbar1'] ) && ! preg_match( '/\bltr\b/', $mce_init['toolbar1'] ) ) {
4075              $mce_init['toolbar1'] .= ',ltr';
4076          }
4077      }
4078  
4079      return $mce_init;
4080  }
4081  
4082  
4083  /**
4084   * Convert smiley code to the icon graphic file equivalent.
4085   *
4086   * You can turn off smilies, by going to the write setting screen and unchecking
4087   * the box, or by setting 'use_smilies' option to false or removing the option.
4088   *
4089   * Plugins may override the default smiley list by setting the $wpsmiliestrans
4090   * to an array, with the key the code the blogger types in and the value the
4091   * image file.
4092   *
4093   * The $wp_smiliessearch global is for the regular expression and is set each
4094   * time the function is called.
4095   *
4096   * The full list of smilies can be found in the function and won't be listed in
4097   * the description. Probably should create a Codex page for it, so that it is
4098   * available.
4099   *
4100   * @global array $wpsmiliestrans
4101   * @global array $wp_smiliessearch
4102   *
4103   * @since 2.2.0
4104   */
4105  function smilies_init() {
4106      global $wpsmiliestrans, $wp_smiliessearch;
4107  
4108      // don't bother setting up smilies if they are disabled
4109      if ( ! get_option( 'use_smilies' ) ) {
4110          return;
4111      }
4112  
4113      if ( ! isset( $wpsmiliestrans ) ) {
4114          $wpsmiliestrans = array(
4115              ':mrgreen:' => 'mrgreen.png',
4116              ':neutral:' => "\xf0\x9f\x98\x90",
4117              ':twisted:' => "\xf0\x9f\x98\x88",
4118              ':arrow:'   => "\xe2\x9e\xa1",
4119              ':shock:'   => "\xf0\x9f\x98\xaf",
4120              ':smile:'   => "\xf0\x9f\x99\x82",
4121              ':???:'     => "\xf0\x9f\x98\x95",
4122              ':cool:'    => "\xf0\x9f\x98\x8e",
4123              ':evil:'    => "\xf0\x9f\x91\xbf",
4124              ':grin:'    => "\xf0\x9f\x98\x80",
4125              ':idea:'    => "\xf0\x9f\x92\xa1",
4126              ':oops:'    => "\xf0\x9f\x98\xb3",
4127              ':razz:'    => "\xf0\x9f\x98\x9b",
4128              ':roll:'    => "\xf0\x9f\x99\x84",
4129              ':wink:'    => "\xf0\x9f\x98\x89",
4130              ':cry:'     => "\xf0\x9f\x98\xa5",
4131              ':eek:'     => "\xf0\x9f\x98\xae",
4132              ':lol:'     => "\xf0\x9f\x98\x86",
4133              ':mad:'     => "\xf0\x9f\x98\xa1",
4134              ':sad:'     => "\xf0\x9f\x99\x81",
4135              '8-)'       => "\xf0\x9f\x98\x8e",
4136              '8-O'       => "\xf0\x9f\x98\xaf",
4137              ':-('       => "\xf0\x9f\x99\x81",
4138              ':-)'       => "\xf0\x9f\x99\x82",
4139              ':-?'       => "\xf0\x9f\x98\x95",
4140              ':-D'       => "\xf0\x9f\x98\x80",
4141              ':-P'       => "\xf0\x9f\x98\x9b",
4142              ':-o'       => "\xf0\x9f\x98\xae",
4143              ':-x'       => "\xf0\x9f\x98\xa1",
4144              ':-|'       => "\xf0\x9f\x98\x90",
4145              ';-)'       => "\xf0\x9f\x98\x89",
4146              // This one transformation breaks regular text with frequency.
4147              //     '8)' => "\xf0\x9f\x98\x8e",
4148              '8O'        => "\xf0\x9f\x98\xaf",
4149              ':('        => "\xf0\x9f\x99\x81",
4150              ':)'        => "\xf0\x9f\x99\x82",
4151              ':?'        => "\xf0\x9f\x98\x95",
4152              ':D'        => "\xf0\x9f\x98\x80",
4153              ':P'        => "\xf0\x9f\x98\x9b",
4154              ':o'        => "\xf0\x9f\x98\xae",
4155              ':x'        => "\xf0\x9f\x98\xa1",
4156              ':|'        => "\xf0\x9f\x98\x90",
4157              ';)'        => "\xf0\x9f\x98\x89",
4158              ':!:'       => "\xe2\x9d\x97",
4159              ':?:'       => "\xe2\x9d\x93",
4160          );
4161      }
4162  
4163      /**
4164       * Filters all the smilies.
4165       *
4166       * This filter must be added before `smilies_init` is run, as
4167       * it is normally only run once to setup the smilies regex.
4168       *
4169       * @since 4.7.0
4170       *
4171       * @param string[] $wpsmiliestrans List of the smilies' hexadecimal representations, keyed by their smily code.
4172       */
4173      $wpsmiliestrans = apply_filters( 'smilies', $wpsmiliestrans );
4174  
4175      if ( count( $wpsmiliestrans ) == 0 ) {
4176          return;
4177      }
4178  
4179      /*
4180       * NOTE: we sort the smilies in reverse key order. This is to make sure
4181       * we match the longest possible smilie (:???: vs :?) as the regular
4182       * expression used below is first-match
4183       */
4184      krsort( $wpsmiliestrans );
4185  
4186      $spaces = wp_spaces_regexp();
4187  
4188      // Begin first "subpattern"
4189      $wp_smiliessearch = '/(?<=' . $spaces . '|^)';
4190  
4191      $subchar = '';
4192      foreach ( (array) $wpsmiliestrans as $smiley => $img ) {
4193          $firstchar = substr( $smiley, 0, 1 );
4194          $rest      = substr( $smiley, 1 );
4195  
4196          // new subpattern?
4197          if ( $firstchar != $subchar ) {
4198              if ( $subchar != '' ) {
4199                  $wp_smiliessearch .= ')(?=' . $spaces . '|$)';  // End previous "subpattern"
4200                  $wp_smiliessearch .= '|(?<=' . $spaces . '|^)'; // Begin another "subpattern"
4201              }
4202              $subchar           = $firstchar;
4203              $wp_smiliessearch .= preg_quote( $firstchar, '/' ) . '(?:';
4204          } else {
4205              $wp_smiliessearch .= '|';
4206          }
4207          $wp_smiliessearch .= preg_quote( $rest, '/' );
4208      }
4209  
4210      $wp_smiliessearch .= ')(?=' . $spaces . '|$)/m';
4211  
4212  }
4213  
4214  /**
4215   * Merge user defined arguments into defaults array.
4216   *
4217   * This function is used throughout WordPress to allow for both string or array
4218   * to be merged into another array.
4219   *
4220   * @since 2.2.0
4221   * @since 2.3.0 `$args` can now also be an object.
4222   *
4223   * @param string|array|object $args     Value to merge with $defaults.
4224   * @param array               $defaults Optional. Array that serves as the defaults. Default empty.
4225   * @return array Merged user defined values with defaults.
4226   */
4227  function wp_parse_args( $args, $defaults = '' ) {
4228      if ( is_object( $args ) ) {
4229          $parsed_args = get_object_vars( $args );
4230      } elseif ( is_array( $args ) ) {
4231          $parsed_args =& $args;
4232      } else {
4233          wp_parse_str( $args, $parsed_args );
4234      }
4235  
4236      if ( is_array( $defaults ) ) {
4237          return array_merge( $defaults, $parsed_args );
4238      }
4239      return $parsed_args;
4240  }
4241  
4242  /**
4243   * Cleans up an array, comma- or space-separated list of scalar values.
4244   *
4245   * @since 5.1.0
4246   *
4247   * @param array|string $list List of values.
4248   * @return array Sanitized array of values.
4249   */
4250  function wp_parse_list( $list ) {
4251      if ( ! is_array( $list ) ) {
4252          return preg_split( '/[\s,]+/', $list, -1, PREG_SPLIT_NO_EMPTY );
4253      }
4254  
4255      return $list;
4256  }
4257  
4258  /**
4259   * Clean up an array, comma- or space-separated list of IDs.
4260   *
4261   * @since 3.0.0
4262   *
4263   * @param array|string $list List of ids.
4264   * @return int[] Sanitized array of IDs.
4265   */
4266  function wp_parse_id_list( $list ) {
4267      $list = wp_parse_list( $list );
4268  
4269      return array_unique( array_map( 'absint', $list ) );
4270  }
4271  
4272  /**
4273   * Clean up an array, comma- or space-separated list of slugs.
4274   *
4275   * @since 4.7.0
4276   *
4277   * @param  array|string $list List of slugs.
4278   * @return string[] Sanitized array of slugs.
4279   */
4280  function wp_parse_slug_list( $list ) {
4281      $list = wp_parse_list( $list );
4282  
4283      return array_unique( array_map( 'sanitize_title', $list ) );
4284  }
4285  
4286  /**
4287   * Extract a slice of an array, given a list of keys.
4288   *
4289   * @since 3.1.0
4290   *
4291   * @param array $array The original array.
4292   * @param array $keys  The list of keys.
4293   * @return array The array slice.
4294   */
4295  function wp_array_slice_assoc( $array, $keys ) {
4296      $slice = array();
4297      foreach ( $keys as $key ) {
4298          if ( isset( $array[ $key ] ) ) {
4299              $slice[ $key ] = $array[ $key ];
4300          }
4301      }
4302  
4303      return $slice;
4304  }
4305  
4306  /**
4307   * Determines if the variable is a numeric-indexed array.
4308   *
4309   * @since 4.4.0
4310   *
4311   * @param mixed $data Variable to check.
4312   * @return bool Whether the variable is a list.
4313   */
4314  function wp_is_numeric_array( $data ) {
4315      if ( ! is_array( $data ) ) {
4316          return false;
4317      }
4318  
4319      $keys        = array_keys( $data );
4320      $string_keys = array_filter( $keys, 'is_string' );
4321      return count( $string_keys ) === 0;
4322  }
4323  
4324  /**
4325   * Filters a list of objects, based on a set of key => value arguments.
4326   *
4327   * @since 3.0.0
4328   * @since 4.7.0 Uses `WP_List_Util` class.
4329   *
4330   * @param array       $list     An array of objects to filter
4331   * @param array       $args     Optional. An array of key => value arguments to match
4332   *                              against each object. Default empty array.
4333   * @param string      $operator Optional. The logical operation to perform. 'or' means
4334   *                              only one element from the array needs to match; 'and'
4335   *                              means all elements must match; 'not' means no elements may
4336   *                              match. Default 'and'.
4337   * @param bool|string $field    A field from the object to place instead of the entire object.
4338   *                              Default false.
4339   * @return array A list of objects or object fields.
4340   */
4341  function wp_filter_object_list( $list, $args = array(), $operator = 'and', $field = false ) {
4342      if ( ! is_array( $list ) ) {
4343          return array();
4344      }
4345  
4346      $util = new WP_List_Util( $list );
4347  
4348      $util->filter( $args, $operator );
4349  
4350      if ( $field ) {
4351          $util->pluck( $field );
4352      }
4353  
4354      return $util->get_output();
4355  }
4356  
4357  /**
4358   * Filters a list of objects, based on a set of key => value arguments.
4359   *
4360   * @since 3.1.0
4361   * @since 4.7.0 Uses `WP_List_Util` class.
4362   *
4363   * @param array  $list     An array of objects to filter.
4364   * @param array  $args     Optional. An array of key => value arguments to match
4365   *                         against each object. Default empty array.
4366   * @param string $operator Optional. The logical operation to perform. 'AND' means
4367   *                         all elements from the array must match. 'OR' means only
4368   *                         one element needs to match. 'NOT' means no elements may
4369   *                         match. Default 'AND'.
4370   * @return array Array of found values.
4371   */
4372  function wp_list_filter( $list, $args = array(), $operator = 'AND' ) {
4373      if ( ! is_array( $list ) ) {
4374          return array();
4375      }
4376  
4377      $util = new WP_List_Util( $list );
4378      return $util->filter( $args, $operator );
4379  }
4380  
4381  /**
4382   * Pluck a certain field out of each object in a list.
4383   *
4384   * This has the same functionality and prototype of
4385   * array_column() (PHP 5.5) but also supports objects.
4386   *
4387   * @since 3.1.0
4388   * @since 4.0.0 $index_key parameter added.
4389   * @since 4.7.0 Uses `WP_List_Util` class.
4390   *
4391   * @param array      $list      List of objects or arrays
4392   * @param int|string $field     Field from the object to place instead of the entire object
4393   * @param int|string $index_key Optional. Field from the object to use as keys for the new array.
4394   *                              Default null.
4395   * @return array Array of found values. If `$index_key` is set, an array of found values with keys
4396   *               corresponding to `$index_key`. If `$index_key` is null, array keys from the original
4397   *               `$list` will be preserved in the results.
4398   */
4399  function wp_list_pluck( $list, $field, $index_key = null ) {
4400      $util = new WP_List_Util( $list );
4401      return $util->pluck( $field, $index_key );
4402  }
4403  
4404  /**
4405   * Sorts a list of objects, based on one or more orderby arguments.
4406   *
4407   * @since 4.7.0
4408   *
4409   * @param array        $list          An array of objects to sort.
4410   * @param string|array $orderby       Optional. Either the field name to order by or an array
4411   *                                    of multiple orderby fields as $orderby => $order.
4412   * @param string       $order         Optional. Either 'ASC' or 'DESC'. Only used if $orderby
4413   *                                    is a string.
4414   * @param bool         $preserve_keys Optional. Whether to preserve keys. Default false.
4415   * @return array The sorted array.
4416   */
4417  function wp_list_sort( $list, $orderby = array(), $order = 'ASC', $preserve_keys = false ) {
4418      if ( ! is_array( $list ) ) {
4419          return array();
4420      }
4421  
4422      $util = new WP_List_Util( $list );
4423      return $util->sort( $orderby, $order, $preserve_keys );
4424  }
4425  
4426  /**
4427   * Determines if Widgets library should be loaded.
4428   *
4429   * Checks to make sure that the widgets library hasn't already been loaded.
4430   * If it hasn't, then it will load the widgets library and run an action hook.
4431   *
4432   * @since 2.2.0
4433   */
4434  function wp_maybe_load_widgets() {
4435      /**
4436       * Filters whether to load the Widgets library.
4437       *
4438       * Passing a falsey value to the filter will effectively short-circuit
4439       * the Widgets library from loading.
4440       *
4441       * @since 2.8.0
4442       *
4443       * @param bool $wp_maybe_load_widgets Whether to load the Widgets library.
4444       *                                    Default true.
4445       */
4446      if ( ! apply_filters( 'load_default_widgets', true ) ) {
4447          return;
4448      }
4449  
4450      require_once ( ABSPATH . WPINC . '/default-widgets.php' );
4451  
4452      add_action( '_admin_menu', 'wp_widgets_add_menu' );
4453  }
4454  
4455  /**
4456   * Append the Widgets menu to the themes main menu.
4457   *
4458   * @since 2.2.0
4459   *
4460   * @global array $submenu
4461   */
4462  function wp_widgets_add_menu() {
4463      global $submenu;
4464  
4465      if ( ! current_theme_supports( 'widgets' ) ) {
4466          return;
4467      }
4468  
4469      $submenu['themes.php'][7] = array( __( 'Widgets' ), 'edit_theme_options', 'widgets.php' );
4470      ksort( $submenu['themes.php'], SORT_NUMERIC );
4471  }
4472  
4473  /**
4474   * Flush all output buffers for PHP 5.2.
4475   *
4476   * Make sure all output buffers are flushed before our singletons are destroyed.
4477   *
4478   * @since 2.2.0
4479   */
4480  function wp_ob_end_flush_all() {
4481      $levels = ob_get_level();
4482      for ( $i = 0; $i < $levels; $i++ ) {
4483          ob_end_flush();
4484      }
4485  }
4486  
4487  /**
4488   * Load custom DB error or display WordPress DB error.
4489   *
4490   * If a file exists in the wp-content directory named db-error.php, then it will
4491   * be loaded instead of displaying the WordPress DB error. If it is not found,
4492   * then the WordPress DB error will be displayed instead.
4493   *
4494   * The WordPress DB error sets the HTTP status header to 500 to try to prevent
4495   * search engines from caching the message. Custom DB messages should do the
4496   * same.
4497   *
4498   * This function was backported to WordPress 2.3.2, but originally was added
4499   * in WordPress 2.5.0.
4500   *
4501   * @since 2.3.2
4502   *
4503   * @global wpdb $wpdb WordPress database abstraction object.
4504   */
4505  function dead_db() {
4506      global $wpdb;
4507  
4508      wp_load_translations_early();
4509  
4510      // Load custom DB error template, if present.
4511      if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
4512          require_once( WP_CONTENT_DIR . '/db-error.php' );
4513          die();
4514      }
4515  
4516      // If installing or in the admin, provide the verbose message.
4517      if ( wp_installing() || defined( 'WP_ADMIN' ) ) {
4518          wp_die( $wpdb->error );
4519      }
4520  
4521      // Otherwise, be terse.
4522      wp_die( '<h1>' . __( 'Error establishing a database connection' ) . '</h1>', __( 'Database Error' ) );
4523  }
4524  
4525  /**
4526   * Convert a value to non-negative integer.
4527   *
4528   * @since 2.5.0
4529   *
4530   * @param mixed $maybeint Data you wish to have converted to a non-negative integer.
4531   * @return int A non-negative integer.
4532   */
4533  function absint( $maybeint ) {
4534      return abs( intval( $maybeint ) );
4535  }
4536  
4537  /**
4538   * Mark a function as deprecated and inform when it has been used.
4539   *
4540   * There is a {@see 'hook deprecated_function_run'} that will be called that can be used
4541   * to get the backtrace up to what file and function called the deprecated
4542   * function.
4543   *
4544   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4545   *
4546   * This function is to be used in every function that is deprecated.
4547   *
4548   * @since 2.5.0
4549   * @since 5.4.0 This function is no longer marked as "private".
4550   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4551   *
4552   * @param string $function    The function that was called.
4553   * @param string $version     The version of WordPress that deprecated the function.
4554   * @param string $replacement Optional. The function that should have been called. Default null.
4555   */
4556  function _deprecated_function( $function, $version, $replacement = null ) {
4557  
4558      /**
4559       * Fires when a deprecated function is called.
4560       *
4561       * @since 2.5.0
4562       *
4563       * @param string $function    The function that was called.
4564       * @param string $replacement The function that should have been called.
4565       * @param string $version     The version of WordPress that deprecated the function.
4566       */
4567      do_action( 'deprecated_function_run', $function, $replacement, $version );
4568  
4569      /**
4570       * Filters whether to trigger an error for deprecated functions.
4571       *
4572       * @since 2.5.0
4573       *
4574       * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
4575       */
4576      if ( WP_DEBUG && apply_filters( 'deprecated_function_trigger_error', true ) ) {
4577          if ( function_exists( '__' ) ) {
4578              if ( ! is_null( $replacement ) ) {
4579                  trigger_error(
4580                      sprintf(
4581                          /* translators: 1: PHP function name, 2: Version number, 3: Alternative function name. */
4582                          __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4583                          $function,
4584                          $version,
4585                          $replacement
4586                      ),
4587                      E_USER_DEPRECATED
4588                  );
4589              } else {
4590                  trigger_error(
4591                      sprintf(
4592                          /* translators: 1: PHP function name, 2: Version number. */
4593                          __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
4594                          $function,
4595                          $version
4596                      ),
4597                      E_USER_DEPRECATED
4598                  );
4599              }
4600          } else {
4601              if ( ! is_null( $replacement ) ) {
4602                  trigger_error(
4603                      sprintf(
4604                          '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
4605                          $function,
4606                          $version,
4607                          $replacement
4608                      ),
4609                      E_USER_DEPRECATED
4610                  );
4611              } else {
4612                  trigger_error(
4613                      sprintf(
4614                          '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
4615                          $function,
4616                          $version
4617                      ),
4618                      E_USER_DEPRECATED
4619                  );
4620              }
4621          }
4622      }
4623  }
4624  
4625  /**
4626   * Marks a constructor as deprecated and informs when it has been used.
4627   *
4628   * Similar to _deprecated_function(), but with different strings. Used to
4629   * remove PHP4 style constructors.
4630   *
4631   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4632   *
4633   * This function is to be used in every PHP4 style constructor method that is deprecated.
4634   *
4635   * @since 4.3.0
4636   * @since 4.5.0 Added the `$parent_class` parameter.
4637   * @since 5.4.0 This function is no longer marked as "private".
4638   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4639   *
4640   * @param string $class        The class containing the deprecated constructor.
4641   * @param string $version      The version of WordPress that deprecated the function.
4642   * @param string $parent_class Optional. The parent class calling the deprecated constructor.
4643   *                             Default empty string.
4644   */
4645  function _deprecated_constructor( $class, $version, $parent_class = '' ) {
4646  
4647      /**
4648       * Fires when a deprecated constructor is called.
4649       *
4650       * @since 4.3.0
4651       * @since 4.5.0 Added the `$parent_class` parameter.
4652       *
4653       * @param string $class        The class containing the deprecated constructor.
4654       * @param string $version      The version of WordPress that deprecated the function.
4655       * @param string $parent_class The parent class calling the deprecated constructor.
4656       */
4657      do_action( 'deprecated_constructor_run', $class, $version, $parent_class );
4658  
4659      /**
4660       * Filters whether to trigger an error for deprecated functions.
4661       *
4662       * `WP_DEBUG` must be true in addition to the filter evaluating to true.
4663       *
4664       * @since 4.3.0
4665       *
4666       * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
4667       */
4668      if ( WP_DEBUG && apply_filters( 'deprecated_constructor_trigger_error', true ) ) {
4669          if ( function_exists( '__' ) ) {
4670              if ( ! empty( $parent_class ) ) {
4671                  trigger_error(
4672                      sprintf(
4673                          /* translators: 1: PHP class name, 2: PHP parent class name, 3: Version number, 4: __construct() method. */
4674                          __( 'The called constructor method for %1$s in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.' ),
4675                          $class,
4676                          $parent_class,
4677                          $version,
4678                          '<code>__construct()</code>'
4679                      ),
4680                      E_USER_DEPRECATED
4681                  );
4682              } else {
4683                  trigger_error(
4684                      sprintf(
4685                          /* translators: 1: PHP class name, 2: Version number, 3: __construct() method. */
4686                          __( 'The called constructor method for %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4687                          $class,
4688                          $version,
4689                          '<code>__construct()</code>'
4690                      ),
4691                      E_USER_DEPRECATED
4692                  );
4693              }
4694          } else {
4695              if ( ! empty( $parent_class ) ) {
4696                  trigger_error(
4697                      sprintf(
4698                          'The called constructor method for %1$s in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.',
4699                          $class,
4700                          $parent_class,
4701                          $version,
4702                          '<code>__construct()</code>'
4703                      ),
4704                      E_USER_DEPRECATED
4705                  );
4706              } else {
4707                  trigger_error(
4708                      sprintf(
4709                          'The called constructor method for %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
4710                          $class,
4711                          $version,
4712                          '<code>__construct()</code>'
4713                      ),
4714                      E_USER_DEPRECATED
4715                  );
4716              }
4717          }
4718      }
4719  
4720  }
4721  
4722  /**
4723   * Mark a file as deprecated and inform when it has been used.
4724   *
4725   * There is a hook {@see 'deprecated_file_included'} that will be called that can be used
4726   * to get the backtrace up to what file and function included the deprecated
4727   * file.
4728   *
4729   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4730   *
4731   * This function is to be used in every file that is deprecated.
4732   *
4733   * @since 2.5.0
4734   * @since 5.4.0 This function is no longer marked as "private".
4735   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4736   *
4737   * @param string $file        The file that was included.
4738   * @param string $version     The version of WordPress that deprecated the file.
4739   * @param string $replacement Optional. The file that should have been included based on ABSPATH.
4740   *                            Default null.
4741   * @param string $message     Optional. A message regarding the change. Default empty.
4742   */
4743  function _deprecated_file( $file, $version, $replacement = null, $message = '' ) {
4744  
4745      /**
4746       * Fires when a deprecated file is called.
4747       *
4748       * @since 2.5.0
4749       *
4750       * @param string $file        The file that was called.
4751       * @param string $replacement The file that should have been included based on ABSPATH.
4752       * @param string $version     The version of WordPress that deprecated the file.
4753       * @param string $message     A message regarding the change.
4754       */
4755      do_action( 'deprecated_file_included', $file, $replacement, $version, $message );
4756  
4757      /**
4758       * Filters whether to trigger an error for deprecated files.
4759       *
4760       * @since 2.5.0
4761       *
4762       * @param bool $trigger Whether to trigger the error for deprecated files. Default true.
4763       */
4764      if ( WP_DEBUG && apply_filters( 'deprecated_file_trigger_error', true ) ) {
4765          $message = empty( $message ) ? '' : ' ' . $message;
4766  
4767          if ( function_exists( '__' ) ) {
4768              if ( ! is_null( $replacement ) ) {
4769                  trigger_error(
4770                      sprintf(
4771                          /* translators: 1: PHP file name, 2: Version number, 3: Alternative file name. */
4772                          __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4773                          $file,
4774                          $version,
4775                          $replacement
4776                      ) . $message,
4777                      E_USER_DEPRECATED
4778                  );
4779              } else {
4780                  trigger_error(
4781                      sprintf(
4782                          /* translators: 1: PHP file name, 2: Version number. */
4783                          __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
4784                          $file,
4785                          $version
4786                      ) . $message,
4787                      E_USER_DEPRECATED
4788                  );
4789              }
4790          } else {
4791              if ( ! is_null( $replacement ) ) {
4792                  trigger_error(
4793                      sprintf(
4794                          '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
4795                          $file,
4796                          $version,
4797                          $replacement
4798                      ) . $message,
4799                      E_USER_DEPRECATED
4800                  );
4801              } else {
4802                  trigger_error(
4803                      sprintf(
4804                          '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
4805                          $file,
4806                          $version
4807                      ) . $message,
4808                      E_USER_DEPRECATED
4809                  );
4810              }
4811          }
4812      }
4813  }
4814  /**
4815   * Mark a function argument as deprecated and inform when it has been used.
4816   *
4817   * This function is to be used whenever a deprecated function argument is used.
4818   * Before this function is called, the argument must be checked for whether it was
4819   * used by comparing it to its default value or evaluating whether it is empty.
4820   * For example:
4821   *
4822   *     if ( ! empty( $deprecated ) ) {
4823   *         _deprecated_argument( __FUNCTION__, '3.0.0' );
4824   *     }
4825   *
4826   * There is a hook deprecated_argument_run that will be called that can be used
4827   * to get the backtrace up to what file and function used the deprecated
4828   * argument.
4829   *
4830   * The current behavior is to trigger a user error if WP_DEBUG is true.
4831   *
4832   * @since 3.0.0
4833   * @since 5.4.0 This function is no longer marked as "private".
4834   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4835   *
4836   * @param string $function The function that was called.
4837   * @param string $version  The version of WordPress that deprecated the argument used.
4838   * @param string $message  Optional. A message regarding the change. Default null.
4839   */
4840  function _deprecated_argument( $function, $version, $message = null ) {
4841  
4842      /**
4843       * Fires when a deprecated argument is called.
4844       *
4845       * @since 3.0.0
4846       *
4847       * @param string $function The function that was called.
4848       * @param string $message  A message regarding the change.
4849       * @param string $version  The version of WordPress that deprecated the argument used.
4850       */
4851      do_action( 'deprecated_argument_run', $function, $message, $version );
4852  
4853      /**
4854       * Filters whether to trigger an error for deprecated arguments.
4855       *
4856       * @since 3.0.0
4857       *
4858       * @param bool $trigger Whether to trigger the error for deprecated arguments. Default true.
4859       */
4860      if ( WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) {
4861          if ( function_exists( '__' ) ) {
4862              if ( ! is_null( $message ) ) {
4863                  trigger_error(
4864                      sprintf(
4865                          /* translators: 1: PHP function name, 2: Version number, 3: Optional message regarding the change. */
4866                          __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s' ),
4867                          $function,
4868                          $version,
4869                          $message
4870                      ),
4871                      E_USER_DEPRECATED
4872                  );
4873              } else {
4874                  trigger_error(
4875                      sprintf(
4876                          /* translators: 1: PHP function name, 2: Version number. */
4877                          __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
4878                          $function,
4879                          $version
4880                      ),
4881                      E_USER_DEPRECATED
4882                  );
4883              }
4884          } else {
4885              if ( ! is_null( $message ) ) {
4886                  trigger_error(
4887                      sprintf(
4888                          '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s',
4889                          $function,
4890                          $version,
4891                          $message
4892                      ),
4893                      E_USER_DEPRECATED
4894                  );
4895              } else {
4896                  trigger_error(
4897                      sprintf(
4898                          '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.',
4899                          $function,
4900                          $version
4901                      ),
4902                      E_USER_DEPRECATED
4903                  );
4904              }
4905          }
4906      }
4907  }
4908  
4909  /**
4910   * Marks a deprecated action or filter hook as deprecated and throws a notice.
4911   *
4912   * Use the {@see 'deprecated_hook_run'} action to get the backtrace describing where
4913   * the deprecated hook was called.
4914   *
4915   * Default behavior is to trigger a user error if `WP_DEBUG` is true.
4916   *
4917   * This function is called by the do_action_deprecated() and apply_filters_deprecated()
4918   * functions, and so generally does not need to be called directly.
4919   *
4920   * @since 4.6.0
4921   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
4922   * @access private
4923   *
4924   * @param string $hook        The hook that was used.
4925   * @param string $version     The version of WordPress that deprecated the hook.
4926   * @param string $replacement Optional. The hook that should have been used.
4927   * @param string $message     Optional. A message regarding the change.
4928   */
4929  function _deprecated_hook( $hook, $version, $replacement = null, $message = null ) {
4930      /**
4931       * Fires when a deprecated hook is called.
4932       *
4933       * @since 4.6.0
4934       *
4935       * @param string $hook        The hook that was called.
4936       * @param string $replacement The hook that should be used as a replacement.
4937       * @param string $version     The version of WordPress that deprecated the argument used.
4938       * @param string $message     A message regarding the change.
4939       */
4940      do_action( 'deprecated_hook_run', $hook, $replacement, $version, $message );
4941  
4942      /**
4943       * Filters whether to trigger deprecated hook errors.
4944       *
4945       * @since 4.6.0
4946       *
4947       * @param bool $trigger Whether to trigger deprecated hook errors. Requires
4948       *                      `WP_DEBUG` to be defined true.
4949       */
4950      if ( WP_DEBUG && apply_filters( 'deprecated_hook_trigger_error', true ) ) {
4951          $message = empty( $message ) ? '' : ' ' . $message;
4952  
4953          if ( ! is_null( $replacement ) ) {
4954              trigger_error(
4955                  sprintf(
4956                      /* translators: 1: WordPress hook name, 2: Version number, 3: Alternative hook name. */
4957                      __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4958                      $hook,
4959                      $version,
4960                      $replacement
4961                  ) . $message,
4962                  E_USER_DEPRECATED
4963              );
4964          } else {
4965              trigger_error(
4966                  sprintf(
4967                      /* translators: 1: WordPress hook name, 2: Version number. */
4968                      __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
4969                      $hook,
4970                      $version
4971                  ) . $message,
4972                  E_USER_DEPRECATED
4973              );
4974          }
4975      }
4976  }
4977  
4978  /**
4979   * Mark something as being incorrectly called.
4980   *
4981   * There is a hook {@see 'doing_it_wrong_run'} that will be called that can be used
4982   * to get the backtrace up to what file and function called the deprecated
4983   * function.
4984   *
4985   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4986   *
4987   * @since 3.1.0
4988   * @since 5.4.0 This function is no longer marked as "private".
4989   *
4990   * @param string $function The function that was called.
4991   * @param string $message  A message explaining what has been done incorrectly.
4992   * @param string $version  The version of WordPress where the message was added.
4993   */
4994  function _doing_it_wrong( $function, $message, $version ) {
4995  
4996      /**
4997       * Fires when the given function is being used incorrectly.
4998       *
4999       * @since 3.1.0
5000       *
5001       * @param string $function The function that was called.
5002       * @param string $message  A message explaining what has been done incorrectly.
5003       * @param string $version  The version of WordPress where the message was added.
5004       */
5005      do_action( 'doing_it_wrong_run', $function, $message, $version );
5006  
5007      /**
5008       * Filters whether to trigger an error for _doing_it_wrong() calls.
5009       *
5010       * @since 3.1.0
5011       * @since 5.1.0 Added the $function, $message and $version parameters.
5012       *
5013       * @param bool   $trigger  Whether to trigger the error for _doing_it_wrong() calls. Default true.
5014       * @param string $function The function that was called.
5015       * @param string $message  A message explaining what has been done incorrectly.
5016       * @param string $version  The version of WordPress where the message was added.
5017       */
5018      if ( WP_DEBUG && apply_filters( 'doing_it_wrong_trigger_error', true, $function, $message, $version ) ) {
5019          if ( function_exists( '__' ) ) {
5020              if ( is_null( $version ) ) {
5021                  $version = '';
5022              } else {
5023                  /* translators: %s: Version number. */
5024                  $version = sprintf( __( '(This message was added in version %s.)' ), $version );
5025              }
5026  
5027              $message .= ' ' . sprintf(
5028                  /* translators: %s: Documentation URL. */
5029                  __( 'Please see <a href="%s">Debugging in WordPress</a> for more information.' ),
5030                  __( 'https://wordpress.org/support/article/debugging-in-wordpress/' )
5031              );
5032  
5033              trigger_error(
5034                  sprintf(
5035                      /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: Version information message. */
5036                      __( '%1$s was called <strong>incorrectly</strong>. %2$s %3$s' ),
5037                      $function,
5038                      $message,
5039                      $version
5040                  ),
5041                  E_USER_NOTICE
5042              );
5043          } else {
5044              if ( is_null( $version ) ) {
5045                  $version = '';
5046              } else {
5047                  $version = sprintf( '(This message was added in version %s.)', $version );
5048              }
5049  
5050              $message .= sprintf(
5051                  ' Please see <a href="%s">Debugging in WordPress</a> for more information.',
5052                  'https://wordpress.org/support/article/debugging-in-wordpress/'
5053              );
5054  
5055              trigger_error(
5056                  sprintf(
5057                      '%1$s was called <strong>incorrectly</strong>. %2$s %3$s',
5058                      $function,
5059                      $message,
5060                      $version
5061                  ),
5062                  E_USER_NOTICE
5063              );
5064          }
5065      }
5066  }
5067  
5068  /**
5069   * Is the server running earlier than 1.5.0 version of lighttpd?
5070   *
5071   * @since 2.5.0
5072   *
5073   * @return bool Whether the server is running lighttpd < 1.5.0.
5074   */
5075  function is_lighttpd_before_150() {
5076      $server_parts    = explode( '/', isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '' );
5077      $server_parts[1] = isset( $server_parts[1] ) ? $server_parts[1] : '';
5078      return  'lighttpd' == $server_parts[0] && -1 == version_compare( $server_parts[1], '1.5.0' );
5079  }
5080  
5081  /**
5082   * Does the specified module exist in the Apache config?
5083   *
5084   * @since 2.5.0
5085   *
5086   * @global bool $is_apache
5087   *
5088   * @param string $mod     The module, e.g. mod_rewrite.
5089   * @param bool   $default Optional. The default return value if the module is not found. Default false.
5090   * @return bool Whether the specified module is loaded.
5091   */
5092  function apache_mod_loaded( $mod, $default = false ) {
5093      global $is_apache;
5094  
5095      if ( ! $is_apache ) {
5096          return false;
5097      }
5098  
5099      if ( function_exists( 'apache_get_modules' ) ) {
5100          $mods = apache_get_modules();
5101          if ( in_array( $mod, $mods ) ) {
5102              return true;
5103          }
5104      } elseif ( function_exists( 'phpinfo' ) && false === strpos( ini_get( 'disable_functions' ), 'phpinfo' ) ) {
5105              ob_start();
5106              phpinfo( 8 );
5107              $phpinfo = ob_get_clean();
5108          if ( false !== strpos( $phpinfo, $mod ) ) {
5109              return true;
5110          }
5111      }
5112      return $default;
5113  }
5114  
5115  /**
5116   * Check if IIS 7+ supports pretty permalinks.
5117   *
5118   * @since 2.8.0
5119   *
5120   * @global bool $is_iis7
5121   *
5122   * @return bool Whether IIS7 supports permalinks.
5123   */
5124  function iis7_supports_permalinks() {
5125      global $is_iis7;
5126  
5127      $supports_permalinks = false;
5128      if ( $is_iis7 ) {
5129          /* First we check if the DOMDocument class exists. If it does not exist, then we cannot
5130           * easily update the xml configuration file, hence we just bail out and tell user that
5131           * pretty permalinks cannot be used.
5132           *
5133           * Next we check if the URL Rewrite Module 1.1 is loaded and enabled for the web site. When
5134           * URL Rewrite 1.1 is loaded it always sets a server variable called 'IIS_UrlRewriteModule'.
5135           * Lastly we make sure that PHP is running via FastCGI. This is important because if it runs
5136           * via ISAPI then pretty permalinks will not work.
5137           */
5138          $supports_permalinks = class_exists( 'DOMDocument', false ) && isset( $_SERVER['IIS_UrlRewriteModule'] ) && ( PHP_SAPI == 'cgi-fcgi' );
5139      }
5140  
5141      /**
5142       * Filters whether IIS 7+ supports pretty permalinks.
5143       *
5144       * @since 2.8.0
5145       *
5146       * @param bool $supports_permalinks Whether IIS7 supports permalinks. Default false.
5147       */
5148      return apply_filters( 'iis7_supports_permalinks', $supports_permalinks );
5149  }
5150  
5151  /**
5152   * Validates a file name and path against an allowed set of rules.
5153   *
5154   * A return value of `1` means the file path contains directory traversal.
5155   *
5156   * A return value of `2` means the file path contains a Windows drive path.
5157   *
5158   * A return value of `3` means the file is not in the allowed files list.
5159   *
5160   * @since 1.2.0
5161   *
5162   * @param string   $file          File path.
5163   * @param string[] $allowed_files Optional. Array of allowed files.
5164   * @return int 0 means nothing is wrong, greater than 0 means something was wrong.
5165   */
5166  function validate_file( $file, $allowed_files = array() ) {
5167      // `../` on its own is not allowed:
5168      if ( '../' === $file ) {
5169          return 1;
5170      }
5171  
5172      // More than one occurence of `../` is not allowed:
5173      if ( preg_match_all( '#\.\./#', $file, $matches, PREG_SET_ORDER ) && ( count( $matches ) > 1 ) ) {
5174          return 1;
5175      }
5176  
5177      // `../` which does not occur at the end of the path is not allowed:
5178      if ( false !== strpos( $file, '../' ) && '../' !== mb_substr( $file, -3, 3 ) ) {
5179          return 1;
5180      }
5181  
5182      // Files not in the allowed file list are not allowed:
5183      if ( ! empty( $allowed_files ) && ! in_array( $file, $allowed_files ) ) {
5184          return 3;
5185      }
5186  
5187      // Absolute Windows drive paths are not allowed:
5188      if ( ':' == substr( $file, 1, 1 ) ) {
5189          return 2;
5190      }
5191  
5192      return 0;
5193  }
5194  
5195  /**
5196   * Whether to force SSL used for the Administration Screens.
5197   *
5198   * @since 2.6.0
5199   *
5200   * @staticvar bool $forced
5201   *
5202   * @param string|bool $force Optional. Whether to force SSL in admin screens. Default null.
5203   * @return bool True if forced, false if not forced.
5204   */
5205  function force_ssl_admin( $force = null ) {
5206      static $forced = false;
5207  
5208      if ( ! is_null( $force ) ) {
5209          $old_forced = $forced;
5210          $forced     = $force;
5211          return $old_forced;
5212      }
5213  
5214      return $forced;
5215  }
5216  
5217  /**
5218   * Guess the URL for the site.
5219   *
5220   * Will remove wp-admin links to retrieve only return URLs not in the wp-admin
5221   * directory.
5222   *
5223   * @since 2.6.0
5224   *
5225   * @return string The guessed URL.
5226   */
5227  function wp_guess_url() {
5228      if ( defined( 'WP_SITEURL' ) && '' != WP_SITEURL ) {
5229          $url = WP_SITEURL;
5230      } else {
5231          $abspath_fix         = str_replace( '\\', '/', ABSPATH );
5232          $script_filename_dir = dirname( $_SERVER['SCRIPT_FILENAME'] );
5233  
5234          // The request is for the admin
5235          if ( strpos( $_SERVER['REQUEST_URI'], 'wp-admin' ) !== false || strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) !== false ) {
5236              $path = preg_replace( '#/(wp-admin/.*|wp-login.php)#i', '', $_SERVER['REQUEST_URI'] );
5237  
5238              // The request is for a file in ABSPATH
5239          } elseif ( $script_filename_dir . '/' == $abspath_fix ) {
5240              // Strip off any file/query params in the path
5241              $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['PHP_SELF'] );
5242  
5243          } else {
5244              if ( false !== strpos( $_SERVER['SCRIPT_FILENAME'], $abspath_fix ) ) {
5245                  // Request is hitting a file inside ABSPATH
5246                  $directory = str_replace( ABSPATH, '', $script_filename_dir );
5247                  // Strip off the sub directory, and any file/query params
5248                  $path = preg_replace( '#/' . preg_quote( $directory, '#' ) . '/[^/]*$#i', '', $_SERVER['REQUEST_URI'] );
5249              } elseif ( false !== strpos( $abspath_fix, $script_filename_dir ) ) {
5250                  // Request is hitting a file above ABSPATH
5251                  $subdirectory = substr( $abspath_fix, strpos( $abspath_fix, $script_filename_dir ) + strlen( $script_filename_dir ) );
5252                  // Strip off any file/query params from the path, appending the sub directory to the installation
5253                  $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['REQUEST_URI'] ) . $subdirectory;
5254              } else {
5255                  $path = $_SERVER['REQUEST_URI'];
5256              }
5257          }
5258  
5259          $schema = is_ssl() ? 'https://' : 'http://'; // set_url_scheme() is not defined yet
5260          $url    = $schema . $_SERVER['HTTP_HOST'] . $path;
5261      }
5262  
5263      return rtrim( $url, '/' );
5264  }
5265  
5266  /**
5267   * Temporarily suspend cache additions.
5268   *
5269   * Stops more data being added to the cache, but still allows cache retrieval.
5270   * This is useful for actions, such as imports, when a lot of data would otherwise
5271   * be almost uselessly added to the cache.
5272   *
5273   * Suspension lasts for a single page load at most. Remember to call this
5274   * function again if you wish to re-enable cache adds earlier.
5275   *
5276   * @since 3.3.0
5277   *
5278   * @staticvar bool $_suspend
5279   *
5280   * @param bool $suspend Optional. Suspends additions if true, re-enables them if false.
5281   * @return bool The current suspend setting
5282   */
5283  function wp_suspend_cache_addition( $suspend = null ) {
5284      static $_suspend = false;
5285  
5286      if ( is_bool( $suspend ) ) {
5287          $_suspend = $suspend;
5288      }
5289  
5290      return $_suspend;
5291  }
5292  
5293  /**
5294   * Suspend cache invalidation.
5295   *
5296   * Turns cache invalidation on and off. Useful during imports where you don't want to do
5297   * invalidations every time a post is inserted. Callers must be sure that what they are
5298   * doing won't lead to an inconsistent cache when invalidation is suspended.
5299   *
5300   * @since 2.7.0
5301   *
5302   * @global bool $_wp_suspend_cache_invalidation
5303   *
5304   * @param bool $suspend Optional. Whether to suspend or enable cache invalidation. Default true.
5305   * @return bool The current suspend setting.
5306   */
5307  function wp_suspend_cache_invalidation( $suspend = true ) {
5308      global $_wp_suspend_cache_invalidation;
5309  
5310      $current_suspend                = $_wp_suspend_cache_invalidation;
5311      $_wp_suspend_cache_invalidation = $suspend;
5312      return $current_suspend;
5313  }
5314  
5315  /**
5316   * Determine whether a site is the main site of the current network.
5317   *
5318   * @since 3.0.0
5319   * @since 4.9.0 The `$network_id` parameter was added.
5320   *
5321   * @param int $site_id    Optional. Site ID to test. Defaults to current site.
5322   * @param int $network_id Optional. Network ID of the network to check for.
5323   *                        Defaults to current network.
5324   * @return bool True if $site_id is the main site of the network, or if not
5325   *              running Multisite.
5326   */
5327  function is_main_site( $site_id = null, $network_id = null ) {
5328      if ( ! is_multisite() ) {
5329          return true;
5330      }
5331  
5332      if ( ! $site_id ) {
5333          $site_id = get_current_blog_id();
5334      }
5335  
5336      $site_id = (int) $site_id;
5337  
5338      return $site_id === get_main_site_id( $network_id );
5339  }
5340  
5341  /**
5342   * Gets the main site ID.
5343   *
5344   * @since 4.9.0
5345   *
5346   * @param int $network_id Optional. The ID of the network for which to get the main site.
5347   *                        Defaults to the current network.
5348   * @return int The ID of the main site.
5349   */
5350  function get_main_site_id( $network_id = null ) {
5351      if ( ! is_multisite() ) {
5352          return get_current_blog_id();
5353      }
5354  
5355      $network = get_network( $network_id );
5356      if ( ! $network ) {
5357          return 0;
5358      }
5359  
5360      return $network->site_id;
5361  }
5362  
5363  /**
5364   * Determine whether a network is the main network of the Multisite installation.
5365   *
5366   * @since 3.7.0
5367   *
5368   * @param int $network_id Optional. Network ID to test. Defaults to current network.
5369   * @return bool True if $network_id is the main network, or if not running Multisite.
5370   */
5371  function is_main_network( $network_id = null ) {
5372      if ( ! is_multisite() ) {
5373          return true;
5374      }
5375  
5376      if ( null === $network_id ) {
5377          $network_id = get_current_network_id();
5378      }
5379  
5380      $network_id = (int) $network_id;
5381  
5382      return ( $network_id === get_main_network_id() );
5383  }
5384  
5385  /**
5386   * Get the main network ID.
5387   *
5388   * @since 4.3.0
5389   *
5390   * @return int The ID of the main network.
5391   */
5392  function get_main_network_id() {
5393      if ( ! is_multisite() ) {
5394          return 1;
5395      }
5396  
5397      $current_network = get_network();
5398  
5399      if ( defined( 'PRIMARY_NETWORK_ID' ) ) {
5400          $main_network_id = PRIMARY_NETWORK_ID;
5401      } elseif ( isset( $current_network->id ) && 1 === (int) $current_network->id ) {
5402          // If the current network has an ID of 1, assume it is the main network.
5403          $main_network_id = 1;
5404      } else {
5405          $_networks       = get_networks(
5406              array(
5407                  'fields' => 'ids',
5408                  'number' => 1,
5409              )
5410          );
5411          $main_network_id = array_shift( $_networks );
5412      }
5413  
5414      /**
5415       * Filters the main network ID.
5416       *
5417       * @since 4.3.0
5418       *
5419       * @param int $main_network_id The ID of the main network.
5420       */
5421      return (int) apply_filters( 'get_main_network_id', $main_network_id );
5422  }
5423  
5424  /**
5425   * Determine whether global terms are enabled.
5426   *
5427   * @since 3.0.0
5428   *
5429   * @staticvar bool $global_terms
5430   *
5431   * @return bool True if multisite and global terms enabled.
5432   */
5433  function global_terms_enabled() {
5434      if ( ! is_multisite() ) {
5435          return false;
5436      }
5437  
5438      static $global_terms = null;
5439      if ( is_null( $global_terms ) ) {
5440  
5441          /**
5442           * Filters whether global terms are enabled.
5443           *
5444           * Passing a non-null value to the filter will effectively short-circuit the function,
5445           * returning the value of the 'global_terms_enabled' site option instead.
5446           *
5447           * @since 3.0.0
5448           *
5449           * @param null $enabled Whether global terms are enabled.
5450           */
5451          $filter = apply_filters( 'global_terms_enabled', null );
5452          if ( ! is_null( $filter ) ) {
5453              $global_terms = (bool) $filter;
5454          } else {
5455              $global_terms = (bool) get_site_option( 'global_terms_enabled', false );
5456          }
5457      }
5458      return $global_terms;
5459  }
5460  
5461  /**
5462   * Determines whether site meta is enabled.
5463   *
5464   * This function checks whether the 'blogmeta' database table exists. The result is saved as
5465   * a setting for the main network, making it essentially a global setting. Subsequent requests
5466   * will refer to this setting instead of running the query.
5467   *
5468   * @since 5.1.0
5469   *
5470   * @global wpdb $wpdb WordPress database abstraction object.
5471   *
5472   * @return bool True if site meta is supported, false otherwise.
5473   */
5474  function is_site_meta_supported() {
5475      global $wpdb;
5476  
5477      if ( ! is_multisite() ) {
5478          return false;
5479      }
5480  
5481      $network_id = get_main_network_id();
5482  
5483      $supported = get_network_option( $network_id, 'site_meta_supported', false );
5484      if ( false === $supported ) {
5485          $supported = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->blogmeta}'" ) ? 1 : 0;
5486  
5487          update_network_option( $network_id, 'site_meta_supported', $supported );
5488      }
5489  
5490      return (bool) $supported;
5491  }
5492  
5493  /**
5494   * gmt_offset modification for smart timezone handling.
5495   *
5496   * Overrides the gmt_offset option if we have a timezone_string available.
5497   *
5498   * @since 2.8.0
5499   *
5500   * @return float|false Timezone GMT offset, false otherwise.
5501   */
5502  function wp_timezone_override_offset() {
5503      $timezone_string = get_option( 'timezone_string' );
5504      if ( ! $timezone_string ) {
5505          return false;
5506      }
5507  
5508      $timezone_object = timezone_open( $timezone_string );
5509      $datetime_object = date_create();
5510      if ( false === $timezone_object || false === $datetime_object ) {
5511          return false;
5512      }
5513      return round( timezone_offset_get( $timezone_object, $datetime_object ) / HOUR_IN_SECONDS, 2 );
5514  }
5515  
5516  /**
5517   * Sort-helper for timezones.
5518   *
5519   * @since 2.9.0
5520   * @access private
5521   *
5522   * @param array $a
5523   * @param array $b
5524   * @return int
5525   */
5526  function _wp_timezone_choice_usort_callback( $a, $b ) {
5527      // Don't use translated versions of Etc
5528      if ( 'Etc' === $a['continent'] && 'Etc' === $b['continent'] ) {
5529          // Make the order of these more like the old dropdown
5530          if ( 'GMT+' === substr( $a['city'], 0, 4 ) && 'GMT+' === substr( $b['city'], 0, 4 ) ) {
5531              return -1 * ( strnatcasecmp( $a['city'], $b['city'] ) );
5532          }
5533          if ( 'UTC' === $a['city'] ) {
5534              if ( 'GMT+' === substr( $b['city'], 0, 4 ) ) {
5535                  return 1;
5536              }
5537              return -1;
5538          }
5539          if ( 'UTC' === $b['city'] ) {
5540              if ( 'GMT+' === substr( $a['city'], 0, 4 ) ) {
5541                  return -1;
5542              }
5543              return 1;
5544          }
5545          return strnatcasecmp( $a['city'], $b['city'] );
5546      }
5547      if ( $a['t_continent'] == $b['t_continent'] ) {
5548          if ( $a['t_city'] == $b['t_city'] ) {
5549              return strnatcasecmp( $a['t_subcity'], $b['t_subcity'] );
5550          }
5551          return strnatcasecmp( $a['t_city'], $b['t_city'] );
5552      } else {
5553          // Force Etc to the bottom of the list
5554          if ( 'Etc' === $a['continent'] ) {
5555              return 1;
5556          }
5557          if ( 'Etc' === $b['continent'] ) {
5558              return -1;
5559          }
5560          return strnatcasecmp( $a['t_continent'], $b['t_continent'] );
5561      }
5562  }
5563  
5564  /**
5565   * Gives a nicely-formatted list of timezone strings.
5566   *
5567   * @since 2.9.0
5568   * @since 4.7.0 Added the `$locale` parameter.
5569   *
5570   * @staticvar bool $mo_loaded
5571   * @staticvar string $locale_loaded
5572   *
5573   * @param string $selected_zone Selected timezone.
5574   * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
5575   * @return string
5576   */
5577  function wp_timezone_choice( $selected_zone, $locale = null ) {
5578      static $mo_loaded = false, $locale_loaded = null;
5579  
5580      $continents = array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' );
5581  
5582      // Load translations for continents and cities.
5583      if ( ! $mo_loaded || $locale !== $locale_loaded ) {
5584          $locale_loaded = $locale ? $locale : get_locale();
5585          $mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
5586          unload_textdomain( 'continents-cities' );
5587          load_textdomain( 'continents-cities', $mofile );
5588          $mo_loaded = true;
5589      }
5590  
5591      $zonen = array();
5592      foreach ( timezone_identifiers_list() as $zone ) {
5593          $zone = explode( '/', $zone );
5594          if ( ! in_array( $zone[0], $continents ) ) {
5595              continue;
5596          }
5597  
5598          // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later
5599          $exists    = array(
5600              0 => ( isset( $zone[0] ) && $zone[0] ),
5601              1 => ( isset( $zone[1] ) &&