[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

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 && ! $timestamp_with_offset ) { // Current time in UTC.
 180          $date = wp_date( $format, null, new DateTimeZone( 'UTC' ) );
 181      } elseif ( ! $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 array 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 array $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 array 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 array $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 mixed        $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 array  $mimes    Optional. Key is the file extension with value as the mime type.
2618   * @return array Values with extension first and mime type.
2619   */
2620  function wp_check_filetype( $filename, $mimes = null ) {
2621      if ( empty( $mimes ) ) {
2622          $mimes = get_allowed_mime_types();
2623      }
2624      $type = false;
2625      $ext  = false;
2626  
2627      foreach ( $mimes as $ext_preg => $mime_match ) {
2628          $ext_preg = '!\.(' . $ext_preg . ')$!i';
2629          if ( preg_match( $ext_preg, $filename, $ext_matches ) ) {
2630              $type = $mime_match;
2631              $ext  = $ext_matches[1];
2632              break;
2633          }
2634      }
2635  
2636      return compact( 'ext', 'type' );
2637  }
2638  
2639  /**
2640   * Attempt to determine the real file type of a file.
2641   *
2642   * If unable to, the file name extension will be used to determine type.
2643   *
2644   * If it's determined that the extension does not match the file's real type,
2645   * then the "proper_filename" value will be set with a proper filename and extension.
2646   *
2647   * Currently this function only supports renaming images validated via wp_get_image_mime().
2648   *
2649   * @since 3.0.0
2650   *
2651   * @param string $file     Full path to the file.
2652   * @param string $filename The name of the file (may differ from $file due to $file being
2653   *                         in a tmp directory).
2654   * @param array   $mimes   Optional. Key is the file extension with value as the mime type.
2655   * @return array Values for the extension, MIME, and either a corrected filename or false
2656   *               if original $filename is valid.
2657   */
2658  function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
2659      $proper_filename = false;
2660  
2661      // Do basic extension validation and MIME mapping
2662      $wp_filetype = wp_check_filetype( $filename, $mimes );
2663      $ext         = $wp_filetype['ext'];
2664      $type        = $wp_filetype['type'];
2665  
2666      // We can't do any further validation without a file to work with
2667      if ( ! file_exists( $file ) ) {
2668          return compact( 'ext', 'type', 'proper_filename' );
2669      }
2670  
2671      $real_mime = false;
2672  
2673      // Validate image types.
2674      if ( $type && 0 === strpos( $type, 'image/' ) ) {
2675  
2676          // Attempt to figure out what type of image it actually is
2677          $real_mime = wp_get_image_mime( $file );
2678  
2679          if ( $real_mime && $real_mime != $type ) {
2680              /**
2681               * Filters the list mapping image mime types to their respective extensions.
2682               *
2683               * @since 3.0.0
2684               *
2685               * @param  array $mime_to_ext Array of image mime types and their matching extensions.
2686               */
2687              $mime_to_ext = apply_filters(
2688                  'getimagesize_mimes_to_exts',
2689                  array(
2690                      'image/jpeg' => 'jpg',
2691                      'image/png'  => 'png',
2692                      'image/gif'  => 'gif',
2693                      'image/bmp'  => 'bmp',
2694                      'image/tiff' => 'tif',
2695                  )
2696              );
2697  
2698              // Replace whatever is after the last period in the filename with the correct extension
2699              if ( ! empty( $mime_to_ext[ $real_mime ] ) ) {
2700                  $filename_parts = explode( '.', $filename );
2701                  array_pop( $filename_parts );
2702                  $filename_parts[] = $mime_to_ext[ $real_mime ];
2703                  $new_filename     = implode( '.', $filename_parts );
2704  
2705                  if ( $new_filename != $filename ) {
2706                      $proper_filename = $new_filename; // Mark that it changed
2707                  }
2708                  // Redefine the extension / MIME
2709                  $wp_filetype = wp_check_filetype( $new_filename, $mimes );
2710                  $ext         = $wp_filetype['ext'];
2711                  $type        = $wp_filetype['type'];
2712              } else {
2713                  // Reset $real_mime and try validating again.
2714                  $real_mime = false;
2715              }
2716          }
2717      }
2718  
2719      // Validate files that didn't get validated during previous checks.
2720      if ( $type && ! $real_mime && extension_loaded( 'fileinfo' ) ) {
2721          $finfo     = finfo_open( FILEINFO_MIME_TYPE );
2722          $real_mime = finfo_file( $finfo, $file );
2723          finfo_close( $finfo );
2724  
2725          // fileinfo often misidentifies obscure files as one of these types
2726          $nonspecific_types = array(
2727              'application/octet-stream',
2728              'application/encrypted',
2729              'application/CDFV2-encrypted',
2730              'application/zip',
2731          );
2732  
2733          /*
2734           * If $real_mime doesn't match the content type we're expecting from the file's extension,
2735           * we need to do some additional vetting. Media types and those listed in $nonspecific_types are
2736           * allowed some leeway, but anything else must exactly match the real content type.
2737           */
2738          if ( in_array( $real_mime, $nonspecific_types, true ) ) {
2739              // File is a non-specific binary type. That's ok if it's a type that generally tends to be binary.
2740              if ( ! in_array( substr( $type, 0, strcspn( $type, '/' ) ), array( 'application', 'video', 'audio' ) ) ) {
2741                  $type = false;
2742                  $ext  = false;
2743              }
2744          } elseif ( 0 === strpos( $real_mime, 'video/' ) || 0 === strpos( $real_mime, 'audio/' ) ) {
2745              /*
2746               * For these types, only the major type must match the real value.
2747               * This means that common mismatches are forgiven: application/vnd.apple.numbers is often misidentified as application/zip,
2748               * and some media files are commonly named with the wrong extension (.mov instead of .mp4)
2749               */
2750              if ( substr( $real_mime, 0, strcspn( $real_mime, '/' ) ) !== substr( $type, 0, strcspn( $type, '/' ) ) ) {
2751                  $type = false;
2752                  $ext  = false;
2753              }
2754          } elseif ( 'text/plain' === $real_mime ) {
2755              // A few common file types are occasionally detected as text/plain; allow those.
2756              if ( ! in_array(
2757                  $type,
2758                  array(
2759                      'text/plain',
2760                      'text/csv',
2761                      'text/richtext',
2762                      'text/tsv',
2763                      'text/vtt',
2764                  )
2765              )
2766              ) {
2767                  $type = false;
2768                  $ext  = false;
2769              }
2770          } elseif ( 'text/rtf' === $real_mime ) {
2771              // Special casing for RTF files.
2772              if ( ! in_array(
2773                  $type,
2774                  array(
2775                      'text/rtf',
2776                      'text/plain',
2777                      'application/rtf',
2778                  )
2779              )
2780              ) {
2781                  $type = false;
2782                  $ext  = false;
2783              }
2784          } else {
2785              if ( $type !== $real_mime ) {
2786                  /*
2787                   * Everything else including image/* and application/*:
2788                   * If the real content type doesn't match the file extension, assume it's dangerous.
2789                   */
2790                  $type = false;
2791                  $ext  = false;
2792              }
2793          }
2794      }
2795  
2796      // The mime type must be allowed
2797      if ( $type ) {
2798          $allowed = get_allowed_mime_types();
2799  
2800          if ( ! in_array( $type, $allowed ) ) {
2801              $type = false;
2802              $ext  = false;
2803          }
2804      }
2805  
2806      /**
2807       * Filters the "real" file type of the given file.
2808       *
2809       * @since 3.0.0
2810       * @since 5.1.0 The $real_mime parameter was added.
2811       *
2812       * @param array       $wp_check_filetype_and_ext File data array containing 'ext', 'type', and
2813       *                                               'proper_filename' keys.
2814       * @param string      $file                      Full path to the file.
2815       * @param string      $filename                  The name of the file (may differ from $file due to
2816       *                                               $file being in a tmp directory).
2817       * @param array       $mimes                     Key is the file extension with value as the mime type.
2818       * @param string|bool $real_mime                 The actual mime type or false if the type cannot be determined.
2819       */
2820      return apply_filters( 'wp_check_filetype_and_ext', compact( 'ext', 'type', 'proper_filename' ), $file, $filename, $mimes, $real_mime );
2821  }
2822  
2823  /**
2824   * Returns the real mime type of an image file.
2825   *
2826   * This depends on exif_imagetype() or getimagesize() to determine real mime types.
2827   *
2828   * @since 4.7.1
2829   *
2830   * @param string $file Full path to the file.
2831   * @return string|false The actual mime type or false if the type cannot be determined.
2832   */
2833  function wp_get_image_mime( $file ) {
2834      /*
2835       * Use exif_imagetype() to check the mimetype if available or fall back to
2836       * getimagesize() if exif isn't avaialbe. If either function throws an Exception
2837       * we assume the file could not be validated.
2838       */
2839      try {
2840          if ( is_callable( 'exif_imagetype' ) ) {
2841              $imagetype = exif_imagetype( $file );
2842              $mime      = ( $imagetype ) ? image_type_to_mime_type( $imagetype ) : false;
2843          } elseif ( function_exists( 'getimagesize' ) ) {
2844              $imagesize = @getimagesize( $file );
2845              $mime      = ( isset( $imagesize['mime'] ) ) ? $imagesize['mime'] : false;
2846          } else {
2847              $mime = false;
2848          }
2849      } catch ( Exception $e ) {
2850          $mime = false;
2851      }
2852  
2853      return $mime;
2854  }
2855  
2856  /**
2857   * Retrieve list of mime types and file extensions.
2858   *
2859   * @since 3.5.0
2860   * @since 4.2.0 Support was added for GIMP (xcf) files.
2861   *
2862   * @return array Array of mime types keyed by the file extension regex corresponding to those types.
2863   */
2864  function wp_get_mime_types() {
2865      /**
2866       * Filters the list of mime types and file extensions.
2867       *
2868       * This filter should be used to add, not remove, mime types. To remove
2869       * mime types, use the {@see 'upload_mimes'} filter.
2870       *
2871       * @since 3.5.0
2872       *
2873       * @param array $wp_get_mime_types Mime types keyed by the file extension regex
2874       *                                 corresponding to those types.
2875       */
2876      return apply_filters(
2877          'mime_types',
2878          array(
2879              // Image formats.
2880              'jpg|jpeg|jpe'                 => 'image/jpeg',
2881              'gif'                          => 'image/gif',
2882              'png'                          => 'image/png',
2883              'bmp'                          => 'image/bmp',
2884              'tiff|tif'                     => 'image/tiff',
2885              'ico'                          => 'image/x-icon',
2886              // Video formats.
2887              'asf|asx'                      => 'video/x-ms-asf',
2888              'wmv'                          => 'video/x-ms-wmv',
2889              'wmx'                          => 'video/x-ms-wmx',
2890              'wm'                           => 'video/x-ms-wm',
2891              'avi'                          => 'video/avi',
2892              'divx'                         => 'video/divx',
2893              'flv'                          => 'video/x-flv',
2894              'mov|qt'                       => 'video/quicktime',
2895              'mpeg|mpg|mpe'                 => 'video/mpeg',
2896              'mp4|m4v'                      => 'video/mp4',
2897              'ogv'                          => 'video/ogg',
2898              'webm'                         => 'video/webm',
2899              'mkv'                          => 'video/x-matroska',
2900              '3gp|3gpp'                     => 'video/3gpp', // Can also be audio
2901              '3g2|3gp2'                     => 'video/3gpp2', // Can also be audio
2902              // Text formats.
2903              'txt|asc|c|cc|h|srt'           => 'text/plain',
2904              'csv'                          => 'text/csv',
2905              'tsv'                          => 'text/tab-separated-values',
2906              'ics'                          => 'text/calendar',
2907              'rtx'                          => 'text/richtext',
2908              'css'                          => 'text/css',
2909              'htm|html'                     => 'text/html',
2910              'vtt'                          => 'text/vtt',
2911              'dfxp'                         => 'application/ttaf+xml',
2912              // Audio formats.
2913              'mp3|m4a|m4b'                  => 'audio/mpeg',
2914              'aac'                          => 'audio/aac',
2915              'ra|ram'                       => 'audio/x-realaudio',
2916              'wav'                          => 'audio/wav',
2917              'ogg|oga'                      => 'audio/ogg',
2918              'flac'                         => 'audio/flac',
2919              'mid|midi'                     => 'audio/midi',
2920              'wma'                          => 'audio/x-ms-wma',
2921              'wax'                          => 'audio/x-ms-wax',
2922              'mka'                          => 'audio/x-matroska',
2923              // Misc application formats.
2924              'rtf'                          => 'application/rtf',
2925              'js'                           => 'application/javascript',
2926              'pdf'                          => 'application/pdf',
2927              'swf'                          => 'application/x-shockwave-flash',
2928              'class'                        => 'application/java',
2929              'tar'                          => 'application/x-tar',
2930              'zip'                          => 'application/zip',
2931              'gz|gzip'                      => 'application/x-gzip',
2932              'rar'                          => 'application/rar',
2933              '7z'                           => 'application/x-7z-compressed',
2934              'exe'                          => 'application/x-msdownload',
2935              'psd'                          => 'application/octet-stream',
2936              'xcf'                          => 'application/octet-stream',
2937              // MS Office formats.
2938              'doc'                          => 'application/msword',
2939              'pot|pps|ppt'                  => 'application/vnd.ms-powerpoint',
2940              'wri'                          => 'application/vnd.ms-write',
2941              'xla|xls|xlt|xlw'              => 'application/vnd.ms-excel',
2942              'mdb'                          => 'application/vnd.ms-access',
2943              'mpp'                          => 'application/vnd.ms-project',
2944              'docx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2945              'docm'                         => 'application/vnd.ms-word.document.macroEnabled.12',
2946              'dotx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
2947              'dotm'                         => 'application/vnd.ms-word.template.macroEnabled.12',
2948              'xlsx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2949              'xlsm'                         => 'application/vnd.ms-excel.sheet.macroEnabled.12',
2950              'xlsb'                         => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
2951              'xltx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
2952              'xltm'                         => 'application/vnd.ms-excel.template.macroEnabled.12',
2953              'xlam'                         => 'application/vnd.ms-excel.addin.macroEnabled.12',
2954              'pptx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2955              'pptm'                         => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
2956              'ppsx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
2957              'ppsm'                         => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
2958              'potx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.template',
2959              'potm'                         => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
2960              'ppam'                         => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
2961              'sldx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
2962              'sldm'                         => 'application/vnd.ms-powerpoint.slide.macroEnabled.12',
2963              'onetoc|onetoc2|onetmp|onepkg' => 'application/onenote',
2964              'oxps'                         => 'application/oxps',
2965              'xps'                          => 'application/vnd.ms-xpsdocument',
2966              // OpenOffice formats.
2967              'odt'                          => 'application/vnd.oasis.opendocument.text',
2968              'odp'                          => 'application/vnd.oasis.opendocument.presentation',
2969              'ods'                          => 'application/vnd.oasis.opendocument.spreadsheet',
2970              'odg'                          => 'application/vnd.oasis.opendocument.graphics',
2971              'odc'                          => 'application/vnd.oasis.opendocument.chart',
2972              'odb'                          => 'application/vnd.oasis.opendocument.database',
2973              'odf'                          => 'application/vnd.oasis.opendocument.formula',
2974              // WordPerfect formats.
2975              'wp|wpd'                       => 'application/wordperfect',
2976              // iWork formats.
2977              'key'                          => 'application/vnd.apple.keynote',
2978              'numbers'                      => 'application/vnd.apple.numbers',
2979              'pages'                        => 'application/vnd.apple.pages',
2980          )
2981      );
2982  }
2983  
2984  /**
2985   * Retrieves the list of common file extensions and their types.
2986   *
2987   * @since 4.6.0
2988   *
2989   * @return array Array of file extensions types keyed by the type of file.
2990   */
2991  function wp_get_ext_types() {
2992  
2993      /**
2994       * Filters file type based on the extension name.
2995       *
2996       * @since 2.5.0
2997       *
2998       * @see wp_ext2type()
2999       *
3000       * @param array $ext2type Multi-dimensional array with extensions for a default set
3001       *                        of file types.
3002       */
3003      return apply_filters(
3004          'ext2type',
3005          array(
3006              'image'       => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico' ),
3007              'audio'       => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ),
3008              'video'       => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ),
3009              'document'    => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ),
3010              'spreadsheet' => array( 'numbers', 'ods', 'xls', 'xlsx', 'xlsm', 'xlsb' ),
3011              'interactive' => array( 'swf', 'key', 'ppt', 'pptx', 'pptm', 'pps', 'ppsx', 'ppsm', 'sldx', 'sldm', 'odp' ),
3012              'text'        => array( 'asc', 'csv', 'tsv', 'txt' ),
3013              'archive'     => array( 'bz2', 'cab', 'dmg', 'gz', 'rar', 'sea', 'sit', 'sqx', 'tar', 'tgz', 'zip', '7z' ),
3014              'code'        => array( 'css', 'htm', 'html', 'php', 'js' ),
3015          )
3016      );
3017  }
3018  
3019  /**
3020   * Retrieve list of allowed mime types and file extensions.
3021   *
3022   * @since 2.8.6
3023   *
3024   * @param int|WP_User $user Optional. User to check. Defaults to current user.
3025   * @return array Array of mime types keyed by the file extension regex corresponding
3026   *               to those types.
3027   */
3028  function get_allowed_mime_types( $user = null ) {
3029      $t = wp_get_mime_types();
3030  
3031      unset( $t['swf'], $t['exe'] );
3032      if ( function_exists( 'current_user_can' ) ) {
3033          $unfiltered = $user ? user_can( $user, 'unfiltered_html' ) : current_user_can( 'unfiltered_html' );
3034      }
3035  
3036      if ( empty( $unfiltered ) ) {
3037          unset( $t['htm|html'], $t['js'] );
3038      }
3039  
3040      /**
3041       * Filters list of allowed mime types and file extensions.
3042       *
3043       * @since 2.0.0
3044       *
3045       * @param array            $t    Mime types keyed by the file extension regex corresponding to
3046       *                               those types. 'swf' and 'exe' removed from full list. 'htm|html' also
3047       *                               removed depending on '$user' capabilities.
3048       * @param int|WP_User|null $user User ID, User object or null if not provided (indicates current user).
3049       */
3050      return apply_filters( 'upload_mimes', $t, $user );
3051  }
3052  
3053  /**
3054   * Display "Are You Sure" message to confirm the action being taken.
3055   *
3056   * If the action has the nonce explain message, then it will be displayed
3057   * along with the "Are you sure?" message.
3058   *
3059   * @since 2.0.4
3060   *
3061   * @param string $action The nonce action.
3062   */
3063  function wp_nonce_ays( $action ) {
3064      if ( 'log-out' == $action ) {
3065          $html = sprintf(
3066              /* translators: %s: Site title. */
3067              __( 'You are attempting to log out of %s' ),
3068              get_bloginfo( 'name' )
3069          );
3070          $html       .= '</p><p>';
3071          $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
3072          $html       .= sprintf(
3073              /* translators: %s: Logout URL. */
3074              __( 'Do you really want to <a href="%s">log out</a>?' ),
3075              wp_logout_url( $redirect_to )
3076          );
3077      } else {
3078          $html = __( 'The link you followed has expired.' );
3079          if ( wp_get_referer() ) {
3080              $html .= '</p><p>';
3081              $html .= sprintf(
3082                  '<a href="%s">%s</a>',
3083                  esc_url( remove_query_arg( 'updated', wp_get_referer() ) ),
3084                  __( 'Please try again.' )
3085              );
3086          }
3087      }
3088  
3089      wp_die( $html, __( 'Something went wrong.' ), 403 );
3090  }
3091  
3092  /**
3093   * Kills WordPress execution and displays HTML page with an error message.
3094   *
3095   * This function complements the `die()` PHP function. The difference is that
3096   * HTML will be displayed to the user. It is recommended to use this function
3097   * only when the execution should not continue any further. It is not recommended
3098   * to call this function very often, and try to handle as many errors as possible
3099   * silently or more gracefully.
3100   *
3101   * As a shorthand, the desired HTTP response code may be passed as an integer to
3102   * the `$title` parameter (the default title would apply) or the `$args` parameter.
3103   *
3104   * @since 2.0.4
3105   * @since 4.1.0 The `$title` and `$args` parameters were changed to optionally accept
3106   *              an integer to be used as the response code.
3107   * @since 5.1.0 The `$link_url`, `$link_text`, and `$exit` arguments were added.
3108   * @since 5.3.0 The `$charset` argument was added.
3109   *
3110   * @global WP_Query $wp_query WordPress Query object.
3111   *
3112   * @param string|WP_Error  $message Optional. Error message. If this is a WP_Error object,
3113   *                                  and not an Ajax or XML-RPC request, the error's messages are used.
3114   *                                  Default empty.
3115   * @param string|int       $title   Optional. Error title. If `$message` is a `WP_Error` object,
3116   *                                  error data with the key 'title' may be used to specify the title.
3117   *                                  If `$title` is an integer, then it is treated as the response
3118   *                                  code. Default empty.
3119   * @param string|array|int $args {
3120   *     Optional. Arguments to control behavior. If `$args` is an integer, then it is treated
3121   *     as the response code. Default empty array.
3122   *
3123   *     @type int    $response       The HTTP response code. Default 200 for Ajax requests, 500 otherwise.
3124   *     @type string $link_url       A URL to include a link to. Only works in combination with $link_text.
3125   *                                  Default empty string.
3126   *     @type string $link_text      A label for the link to include. Only works in combination with $link_url.
3127   *                                  Default empty string.
3128   *     @type bool   $back_link      Whether to include a link to go back. Default false.
3129   *     @type string $text_direction The text direction. This is only useful internally, when WordPress
3130   *                                  is still loading and the site's locale is not set up yet. Accepts 'rtl'.
3131   *                                  Default is the value of is_rtl().
3132   *     @type string $charset        Character set of the HTML output. Default 'utf-8'.
3133   *     @type string $code           Error code to use. Default is 'wp_die', or the main error code if $message
3134   *                                  is a WP_Error.
3135   *     @type bool   $exit           Whether to exit the process after completion. Default true.
3136   * }
3137   */
3138  function wp_die( $message = '', $title = '', $args = array() ) {
3139      global $wp_query;
3140  
3141      if ( is_int( $args ) ) {
3142          $args = array( 'response' => $args );
3143      } elseif ( is_int( $title ) ) {
3144          $args  = array( 'response' => $title );
3145          $title = '';
3146      }
3147  
3148      if ( wp_doing_ajax() ) {
3149          /**
3150           * Filters the callback for killing WordPress execution for Ajax requests.
3151           *
3152           * @since 3.4.0
3153           *
3154           * @param callable $function Callback function name.
3155           */
3156          $function = apply_filters( 'wp_die_ajax_handler', '_ajax_wp_die_handler' );
3157      } elseif ( wp_is_json_request() ) {
3158          /**
3159           * Filters the callback for killing WordPress execution for JSON requests.
3160           *
3161           * @since 5.1.0
3162           *
3163           * @param callable $function Callback function name.
3164           */
3165          $function = apply_filters( 'wp_die_json_handler', '_json_wp_die_handler' );
3166      } elseif ( wp_is_jsonp_request() ) {
3167          /**
3168           * Filters the callback for killing WordPress execution for JSONP requests.
3169           *
3170           * @since 5.2.0
3171           *
3172           * @param callable $function Callback function name.
3173           */
3174          $function = apply_filters( 'wp_die_jsonp_handler', '_jsonp_wp_die_handler' );
3175      } elseif ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
3176          /**
3177           * Filters the callback for killing WordPress execution for XML-RPC requests.
3178           *
3179           * @since 3.4.0
3180           *
3181           * @param callable $function Callback function name.
3182           */
3183          $function = apply_filters( 'wp_die_xmlrpc_handler', '_xmlrpc_wp_die_handler' );
3184      } elseif ( wp_is_xml_request()
3185          || isset( $wp_query ) &&
3186              ( function_exists( 'is_feed' ) && is_feed()
3187              || function_exists( 'is_comment_feed' ) && is_comment_feed()
3188              || function_exists( 'is_trackback' ) && is_trackback() ) ) {
3189          /**
3190           * Filters the callback for killing WordPress execution for XML requests.
3191           *
3192           * @since 5.2.0
3193           *
3194           * @param callable $function Callback function name.
3195           */
3196          $function = apply_filters( 'wp_die_xml_handler', '_xml_wp_die_handler' );
3197      } else {
3198          /**
3199           * Filters the callback for killing WordPress execution for all non-Ajax, non-JSON, non-XML requests.
3200           *
3201           * @since 3.0.0
3202           *
3203           * @param callable $function Callback function name.
3204           */
3205          $function = apply_filters( 'wp_die_handler', '_default_wp_die_handler' );
3206      }
3207  
3208      call_user_func( $function, $message, $title, $args );
3209  }
3210  
3211  /**
3212   * Kills WordPress execution and displays HTML page with an error message.
3213   *
3214   * This is the default handler for wp_die(). If you want a custom one,
3215   * you can override this using the {@see 'wp_die_handler'} filter in wp_die().
3216   *
3217   * @since 3.0.0
3218   * @access private
3219   *
3220   * @param string|WP_Error $message Error message or WP_Error object.
3221   * @param string          $title   Optional. Error title. Default empty.
3222   * @param string|array    $args    Optional. Arguments to control behavior. Default empty array.
3223   */
3224  function _default_wp_die_handler( $message, $title = '', $args = array() ) {
3225      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3226  
3227      if ( is_string( $message ) ) {
3228          if ( ! empty( $parsed_args['additional_errors'] ) ) {
3229              $message = array_merge(
3230                  array( $message ),
3231                  wp_list_pluck( $parsed_args['additional_errors'], 'message' )
3232              );
3233              $message = "<ul>\n\t\t<li>" . join( "</li>\n\t\t<li>", $message ) . "</li>\n\t</ul>";
3234          }
3235  
3236          $message = sprintf(
3237              '<div class="wp-die-message">%s</div>',
3238              $message
3239          );
3240      }
3241  
3242      $have_gettext = function_exists( '__' );
3243  
3244      if ( ! empty( $parsed_args['link_url'] ) && ! empty( $parsed_args['link_text'] ) ) {
3245          $link_url = $parsed_args['link_url'];
3246          if ( function_exists( 'esc_url' ) ) {
3247              $link_url = esc_url( $link_url );
3248          }
3249          $link_text = $parsed_args['link_text'];
3250          $message  .= "\n<p><a href='{$link_url}'>{$link_text}</a></p>";
3251      }
3252  
3253      if ( isset( $parsed_args['back_link'] ) && $parsed_args['back_link'] ) {
3254          $back_text = $have_gettext ? __( '&laquo; Back' ) : '&laquo; Back';
3255          $message  .= "\n<p><a href='javascript:history.back()'>$back_text</a></p>";
3256      }
3257  
3258      if ( ! did_action( 'admin_head' ) ) :
3259          if ( ! headers_sent() ) {
3260              header( "Content-Type: text/html; charset={$parsed_args['charset']}" );
3261              status_header( $parsed_args['response'] );
3262              nocache_headers();
3263          }
3264  
3265          $text_direction = $parsed_args['text_direction'];
3266          if ( function_exists( 'language_attributes' ) && function_exists( 'is_rtl' ) ) {
3267              $dir_attr = get_language_attributes();
3268          } else {
3269              $dir_attr = "dir='$text_direction'";
3270          }
3271          ?>
3272  <!DOCTYPE html>
3273  <html xmlns="http://www.w3.org/1999/xhtml" <?php echo $dir_attr; ?>>
3274  <head>
3275      <meta http-equiv="Content-Type" content="text/html; charset=<?php echo $parsed_args['charset']; ?>" />
3276      <meta name="viewport" content="width=device-width">
3277          <?php
3278          if ( function_exists( 'wp_no_robots' ) ) {
3279              wp_no_robots();
3280          }
3281          ?>
3282      <title><?php echo $title; ?></title>
3283      <style type="text/css">
3284          html {
3285              background: #f1f1f1;
3286          }
3287          body {
3288              background: #fff;
3289              color: #444;
3290              font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
3291              margin: 2em auto;
3292              padding: 1em 2em;
3293              max-width: 700px;
3294              -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
3295              box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
3296          }
3297          h1 {
3298              border-bottom: 1px solid #dadada;
3299              clear: both;
3300              color: #666;
3301              font-size: 24px;
3302              margin: 30px 0 0 0;
3303              padding: 0;
3304              padding-bottom: 7px;
3305          }
3306          #error-page {
3307              margin-top: 50px;
3308          }
3309          #error-page p,
3310          #error-page .wp-die-message {
3311              font-size: 14px;
3312              line-height: 1.5;
3313              margin: 25px 0 20px;
3314          }
3315          #error-page code {
3316              font-family: Consolas, Monaco, monospace;
3317          }
3318          ul li {
3319              margin-bottom: 10px;
3320              font-size: 14px ;
3321          }
3322          a {
3323              color: #0073aa;
3324          }
3325          a:hover,
3326          a:active {
3327              color: #00a0d2;
3328          }
3329          a:focus {
3330              color: #124964;
3331              -webkit-box-shadow:
3332                  0 0 0 1px #5b9dd9,
3333                  0 0 2px 1px rgba(30, 140, 190, 0.8);
3334              box-shadow:
3335                  0 0 0 1px #5b9dd9,
3336                  0 0 2px 1px rgba(30, 140, 190, 0.8);
3337              outline: none;
3338          }
3339          .button {
3340              background: #f7f7f7;
3341              border: 1px solid #ccc;
3342              color: #555;
3343              display: inline-block;
3344              text-decoration: none;
3345              font-size: 13px;
3346              line-height: 2;
3347              height: 28px;
3348              margin: 0;
3349              padding: 0 10px 1px;
3350              cursor: pointer;
3351              -webkit-border-radius: 3px;
3352              -webkit-appearance: none;
3353              border-radius: 3px;
3354              white-space: nowrap;
3355              -webkit-box-sizing: border-box;
3356              -moz-box-sizing:    border-box;
3357              box-sizing:         border-box;
3358  
3359              -webkit-box-shadow: 0 1px 0 #ccc;
3360              box-shadow: 0 1px 0 #ccc;
3361              vertical-align: top;
3362          }
3363  
3364          .button.button-large {
3365              height: 30px;
3366              line-height: 2.15384615;
3367              padding: 0 12px 2px;
3368          }
3369  
3370          .button:hover,
3371          .button:focus {
3372              background: #fafafa;
3373              border-color: #999;
3374              color: #23282d;
3375          }
3376  
3377          .button:focus {
3378              border-color: #5b9dd9;
3379              -webkit-box-shadow: 0 0 3px rgba(0, 115, 170, 0.8);
3380              box-shadow: 0 0 3px rgba(0, 115, 170, 0.8);
3381              outline: none;
3382          }
3383  
3384          .button:active {
3385              background: #eee;
3386              border-color: #999;
3387              -webkit-box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
3388              box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
3389          }
3390  
3391          <?php
3392          if ( 'rtl' == $text_direction ) {
3393              echo 'body { font-family: Tahoma, Arial; }';
3394          }
3395          ?>
3396      </style>
3397  </head>
3398  <body id="error-page">
3399  <?php endif; // ! did_action( 'admin_head' ) ?>
3400      <?php echo $message; ?>
3401  </body>
3402  </html>
3403      <?php
3404      if ( $parsed_args['exit'] ) {
3405          die();
3406      }
3407  }
3408  
3409  /**
3410   * Kills WordPress execution and displays Ajax response with an error message.
3411   *
3412   * This is the handler for wp_die() when processing Ajax requests.
3413   *
3414   * @since 3.4.0
3415   * @access private
3416   *
3417   * @param string       $message Error message.
3418   * @param string       $title   Optional. Error title (unused). Default empty.
3419   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3420   */
3421  function _ajax_wp_die_handler( $message, $title = '', $args = array() ) {
3422      // Set default 'response' to 200 for AJAX requests.
3423      $args = wp_parse_args(
3424          $args,
3425          array( 'response' => 200 )
3426      );
3427  
3428      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3429  
3430      if ( ! headers_sent() ) {
3431          // This is intentional. For backward-compatibility, support passing null here.
3432          if ( null !== $args['response'] ) {
3433              status_header( $parsed_args['response'] );
3434          }
3435          nocache_headers();
3436      }
3437  
3438      if ( is_scalar( $message ) ) {
3439          $message = (string) $message;
3440      } else {
3441          $message = '0';
3442      }
3443  
3444      if ( $parsed_args['exit'] ) {
3445          die( $message );
3446      }
3447  
3448      echo $message;
3449  }
3450  
3451  /**
3452   * Kills WordPress execution and displays JSON response with an error message.
3453   *
3454   * This is the handler for wp_die() when processing JSON requests.
3455   *
3456   * @since 5.1.0
3457   * @access private
3458   *
3459   * @param string       $message Error message.
3460   * @param string       $title   Optional. Error title. Default empty.
3461   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3462   */
3463  function _json_wp_die_handler( $message, $title = '', $args = array() ) {
3464      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3465  
3466      $data = array(
3467          'code'              => $parsed_args['code'],
3468          'message'           => $message,
3469          'data'              => array(
3470              'status' => $parsed_args['response'],
3471          ),
3472          'additional_errors' => $parsed_args['additional_errors'],
3473      );
3474  
3475      if ( ! headers_sent() ) {
3476          header( "Content-Type: application/json; charset={$parsed_args['charset']}" );
3477          if ( null !== $parsed_args['response'] ) {
3478              status_header( $parsed_args['response'] );
3479          }
3480          nocache_headers();
3481      }
3482  
3483      echo wp_json_encode( $data );
3484      if ( $parsed_args['exit'] ) {
3485          die();
3486      }
3487  }
3488  
3489  /**
3490   * Kills WordPress execution and displays JSONP response with an error message.
3491   *
3492   * This is the handler for wp_die() when processing JSONP requests.
3493   *
3494   * @since 5.2.0
3495   * @access private
3496   *
3497   * @param string       $message Error message.
3498   * @param string       $title   Optional. Error title. Default empty.
3499   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3500   */
3501  function _jsonp_wp_die_handler( $message, $title = '', $args = array() ) {
3502      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3503  
3504      $data = array(
3505          'code'              => $parsed_args['code'],
3506          'message'           => $message,
3507          'data'              => array(
3508              'status' => $parsed_args['response'],
3509          ),
3510          'additional_errors' => $parsed_args['additional_errors'],
3511      );
3512  
3513      if ( ! headers_sent() ) {
3514          header( "Content-Type: application/javascript; charset={$parsed_args['charset']}" );
3515          header( 'X-Content-Type-Options: nosniff' );
3516          header( 'X-Robots-Tag: noindex' );
3517          if ( null !== $parsed_args['response'] ) {
3518              status_header( $parsed_args['response'] );
3519          }
3520          nocache_headers();
3521      }
3522  
3523      $result         = wp_json_encode( $data );
3524      $jsonp_callback = $_GET['_jsonp'];
3525      echo '/**/' . $jsonp_callback . '(' . $result . ')';
3526      if ( $parsed_args['exit'] ) {
3527          die();
3528      }
3529  }
3530  
3531  /**
3532   * Kills WordPress execution and displays XML response with an error message.
3533   *
3534   * This is the handler for wp_die() when processing XMLRPC requests.
3535   *
3536   * @since 3.2.0
3537   * @access private
3538   *
3539   * @global wp_xmlrpc_server $wp_xmlrpc_server
3540   *
3541   * @param string       $message Error message.
3542   * @param string       $title   Optional. Error title. Default empty.
3543   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3544   */
3545  function _xmlrpc_wp_die_handler( $message, $title = '', $args = array() ) {
3546      global $wp_xmlrpc_server;
3547  
3548      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3549  
3550      if ( ! headers_sent() ) {
3551          nocache_headers();
3552      }
3553  
3554      if ( $wp_xmlrpc_server ) {
3555          $error = new IXR_Error( $parsed_args['response'], $message );
3556          $wp_xmlrpc_server->output( $error->getXml() );
3557      }
3558      if ( $parsed_args['exit'] ) {
3559          die();
3560      }
3561  }
3562  
3563  /**
3564   * Kills WordPress execution and displays XML response with an error message.
3565   *
3566   * This is the handler for wp_die() when processing XML requests.
3567   *
3568   * @since 5.2.0
3569   * @access private
3570   *
3571   * @param string       $message Error message.
3572   * @param string       $title   Optional. Error title. Default empty.
3573   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3574   */
3575  function _xml_wp_die_handler( $message, $title = '', $args = array() ) {
3576      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3577  
3578      $message = htmlspecialchars( $message );
3579      $title   = htmlspecialchars( $title );
3580  
3581      $xml = <<<EOD
3582  <error>
3583      <code>{$parsed_args['code']}</code>
3584      <title><![CDATA[{$title}]]></title>
3585      <message><![CDATA[{$message}]]></message>
3586      <data>
3587          <status>{$parsed_args['response']}</status>
3588      </data>
3589  </error>
3590  
3591  EOD;
3592  
3593      if ( ! headers_sent() ) {
3594          header( "Content-Type: text/xml; charset={$parsed_args['charset']}" );
3595          if ( null !== $parsed_args['response'] ) {
3596              status_header( $parsed_args['response'] );
3597          }
3598          nocache_headers();
3599      }
3600  
3601      echo $xml;
3602      if ( $parsed_args['exit'] ) {
3603          die();
3604      }
3605  }
3606  
3607  /**
3608   * Kills WordPress execution and displays an error message.
3609   *
3610   * This is the handler for wp_die() when processing APP requests.
3611   *
3612   * @since 3.4.0
3613   * @since 5.1.0 Added the $title and $args parameters.
3614   * @access private
3615   *
3616   * @param string       $message Optional. Response to print. Default empty.
3617   * @param string       $title   Optional. Error title (unused). Default empty.
3618   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
3619   */
3620  function _scalar_wp_die_handler( $message = '', $title = '', $args = array() ) {
3621      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3622  
3623      if ( $parsed_args['exit'] ) {
3624          if ( is_scalar( $message ) ) {
3625              die( (string) $message );
3626          }
3627          die();
3628      }
3629  
3630      if ( is_scalar( $message ) ) {
3631          echo (string) $message;
3632      }
3633  }
3634  
3635  /**
3636   * Processes arguments passed to wp_die() consistently for its handlers.
3637   *
3638   * @since 5.1.0
3639   * @access private
3640   *
3641   * @param string|WP_Error $message Error message or WP_Error object.
3642   * @param string          $title   Optional. Error title. Default empty.
3643   * @param string|array    $args    Optional. Arguments to control behavior. Default empty array.
3644   * @return array {
3645   *     Processed arguments.
3646   *
3647   *     @type string $0 Error message.
3648   *     @type string $1 Error title.
3649   *     @type array  $2 Arguments to control behavior.
3650   * }
3651   */
3652  function _wp_die_process_input( $message, $title = '', $args = array() ) {
3653      $defaults = array(
3654          'response'          => 0,
3655          'code'              => '',
3656          'exit'              => true,
3657          'back_link'         => false,
3658          'link_url'          => '',
3659          'link_text'         => '',
3660          'text_direction'    => '',
3661          'charset'           => 'utf-8',
3662          'additional_errors' => array(),
3663      );
3664  
3665      $args = wp_parse_args( $args, $defaults );
3666  
3667      if ( function_exists( 'is_wp_error' ) && is_wp_error( $message ) ) {
3668          if ( ! empty( $message->errors ) ) {
3669              $errors = array();
3670              foreach ( (array) $message->errors as $error_code => $error_messages ) {
3671                  foreach ( (array) $error_messages as $error_message ) {
3672                      $errors[] = array(
3673                          'code'    => $error_code,
3674                          'message' => $error_message,
3675                          'data'    => $message->get_error_data( $error_code ),
3676                      );
3677                  }
3678              }
3679  
3680              $message = $errors[0]['message'];
3681              if ( empty( $args['code'] ) ) {
3682                  $args['code'] = $errors[0]['code'];
3683              }
3684              if ( empty( $args['response'] ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['status'] ) ) {
3685                  $args['response'] = $errors[0]['data']['status'];
3686              }
3687              if ( empty( $title ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['title'] ) ) {
3688                  $title = $errors[0]['data']['title'];
3689              }
3690  
3691              unset( $errors[0] );
3692              $args['additional_errors'] = array_values( $errors );
3693          } else {
3694              $message = '';
3695          }
3696      }
3697  
3698      $have_gettext = function_exists( '__' );
3699  
3700      // The $title and these specific $args must always have a non-empty value.
3701      if ( empty( $args['code'] ) ) {
3702          $args['code'] = 'wp_die';
3703      }
3704      if ( empty( $args['response'] ) ) {
3705          $args['response'] = 500;
3706      }
3707      if ( empty( $title ) ) {
3708          $title = $have_gettext ? __( 'WordPress &rsaquo; Error' ) : 'WordPress &rsaquo; Error';
3709      }
3710      if ( empty( $args['text_direction'] ) || ! in_array( $args['text_direction'], array( 'ltr', 'rtl' ), true ) ) {
3711          $args['text_direction'] = 'ltr';
3712          if ( function_exists( 'is_rtl' ) && is_rtl() ) {
3713              $args['text_direction'] = 'rtl';
3714          }
3715      }
3716  
3717      if ( ! empty( $args['charset'] ) ) {
3718          $args['charset'] = _canonical_charset( $args['charset'] );
3719      }
3720  
3721      return array( $message, $title, $args );
3722  }
3723  
3724  /**
3725   * Encode a variable into JSON, with some sanity checks.
3726   *
3727   * @since 4.1.0
3728   * @since 5.3.0 No longer handles support for PHP < 5.6.
3729   *
3730   * @param mixed $data    Variable (usually an array or object) to encode as JSON.
3731   * @param int   $options Optional. Options to be passed to json_encode(). Default 0.
3732   * @param int   $depth   Optional. Maximum depth to walk through $data. Must be
3733   *                       greater than 0. Default 512.
3734   * @return string|false The JSON encoded string, or false if it cannot be encoded.
3735   */
3736  function wp_json_encode( $data, $options = 0, $depth = 512 ) {
3737      $json = json_encode( $data, $options, $depth );
3738  
3739      // If json_encode() was successful, no need to do more sanity checking.
3740      if ( false !== $json ) {
3741          return $json;
3742      }
3743  
3744      try {
3745          $data = _wp_json_sanity_check( $data, $depth );
3746      } catch ( Exception $e ) {
3747          return false;
3748      }
3749  
3750      return json_encode( $data, $options, $depth );
3751  }
3752  
3753  /**
3754   * Perform sanity checks on data that shall be encoded to JSON.
3755   *
3756   * @ignore
3757   * @since 4.1.0
3758   * @access private
3759   *
3760   * @see wp_json_encode()
3761   *
3762   * @param mixed $data  Variable (usually an array or object) to encode as JSON.
3763   * @param int   $depth Maximum depth to walk through $data. Must be greater than 0.
3764   * @return mixed The sanitized data that shall be encoded to JSON.
3765   */
3766  function _wp_json_sanity_check( $data, $depth ) {
3767      if ( $depth < 0 ) {
3768          throw new Exception( 'Reached depth limit' );
3769      }
3770  
3771      if ( is_array( $data ) ) {
3772          $output = array();
3773          foreach ( $data as $id => $el ) {
3774              // Don't forget to sanitize the ID!
3775              if ( is_string( $id ) ) {
3776                  $clean_id = _wp_json_convert_string( $id );
3777              } else {
3778                  $clean_id = $id;
3779              }
3780  
3781              // Check the element type, so that we're only recursing if we really have to.
3782              if ( is_array( $el ) || is_object( $el ) ) {
3783                  $output[ $clean_id ] = _wp_json_sanity_check( $el, $depth - 1 );
3784              } elseif ( is_string( $el ) ) {
3785                  $output[ $clean_id ] = _wp_json_convert_string( $el );
3786              } else {
3787                  $output[ $clean_id ] = $el;
3788              }
3789          }
3790      } elseif ( is_object( $data ) ) {
3791          $output = new stdClass;
3792          foreach ( $data as $id => $el ) {
3793              if ( is_string( $id ) ) {
3794                  $clean_id = _wp_json_convert_string( $id );
3795              } else {
3796                  $clean_id = $id;
3797              }
3798  
3799              if ( is_array( $el ) || is_object( $el ) ) {
3800                  $output->$clean_id = _wp_json_sanity_check( $el, $depth - 1 );
3801              } elseif ( is_string( $el ) ) {
3802                  $output->$clean_id = _wp_json_convert_string( $el );
3803              } else {
3804                  $output->$clean_id = $el;
3805              }
3806          }
3807      } elseif ( is_string( $data ) ) {
3808          return _wp_json_convert_string( $data );
3809      } else {
3810          return $data;
3811      }
3812  
3813      return $output;
3814  }
3815  
3816  /**
3817   * Convert a string to UTF-8, so that it can be safely encoded to JSON.
3818   *
3819   * @ignore
3820   * @since 4.1.0
3821   * @access private
3822   *
3823   * @see _wp_json_sanity_check()
3824   *
3825   * @staticvar bool $use_mb
3826   *
3827   * @param string $string The string which is to be converted.
3828   * @return string The checked string.
3829   */
3830  function _wp_json_convert_string( $string ) {
3831      static $use_mb = null;
3832      if ( is_null( $use_mb ) ) {
3833          $use_mb = function_exists( 'mb_convert_encoding' );
3834      }
3835  
3836      if ( $use_mb ) {
3837          $encoding = mb_detect_encoding( $string, mb_detect_order(), true );
3838          if ( $encoding ) {
3839              return mb_convert_encoding( $string, 'UTF-8', $encoding );
3840          } else {
3841              return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
3842          }
3843      } else {
3844          return wp_check_invalid_utf8( $string, true );
3845      }
3846  }
3847  
3848  /**
3849   * Prepares response data to be serialized to JSON.
3850   *
3851   * This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
3852   *
3853   * @ignore
3854   * @since      4.4.0
3855   * @deprecated 5.3.0 This function is no longer needed as support for PHP 5.2-5.3
3856   *                   has been dropped.
3857   * @access     private
3858   *
3859   * @param mixed $data Native representation.
3860   * @return bool|int|float|null|string|array Data ready for `json_encode()`.
3861   */
3862  function _wp_json_prepare_data( $data ) {
3863      _deprecated_function( __FUNCTION__, '5.3.0' );
3864      return $data;
3865  }
3866  
3867  /**
3868   * Send a JSON response back to an Ajax request.
3869   *
3870   * @since 3.5.0
3871   * @since 4.7.0 The `$status_code` parameter was added.
3872   *
3873   * @param mixed $response    Variable (usually an array or object) to encode as JSON,
3874   *                           then print and die.
3875   * @param int   $status_code The HTTP status code to output.
3876   */
3877  function wp_send_json( $response, $status_code = null ) {
3878      if ( ! headers_sent() ) {
3879          header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
3880          if ( null !== $status_code ) {
3881              status_header( $status_code );
3882          }
3883      }
3884  
3885      echo wp_json_encode( $response );
3886  
3887      if ( wp_doing_ajax() ) {
3888          wp_die(
3889              '',
3890              '',
3891              array(
3892                  'response' => null,
3893              )
3894          );
3895      } else {
3896          die;
3897      }
3898  }
3899  
3900  /**
3901   * Send a JSON response back to an Ajax request, indicating success.
3902   *
3903   * @since 3.5.0
3904   * @since 4.7.0 The `$status_code` parameter was added.
3905   *
3906   * @param mixed $data        Data to encode as JSON, then print and die.
3907   * @param int   $status_code The HTTP status code to output.
3908   */
3909  function wp_send_json_success( $data = null, $status_code = null ) {
3910      $response = array( 'success' => true );
3911  
3912      if ( isset( $data ) ) {
3913          $response['data'] = $data;
3914      }
3915  
3916      wp_send_json( $response, $status_code );
3917  }
3918  
3919  /**
3920   * Send a JSON response back to an Ajax request, indicating failure.
3921   *
3922   * If the `$data` parameter is a WP_Error object, the errors
3923   * within the object are processed and output as an array of error
3924   * codes and corresponding messages. All other types are output
3925   * without further processing.
3926   *
3927   * @since 3.5.0
3928   * @since 4.1.0 The `$data` parameter is now processed if a WP_Error object is passed in.
3929   * @since 4.7.0 The `$status_code` parameter was added.
3930   *
3931   * @param mixed $data        Data to encode as JSON, then print and die.
3932   * @param int   $status_code The HTTP status code to output.
3933   */
3934  function wp_send_json_error( $data = null, $status_code = null ) {
3935      $response = array( 'success' => false );
3936  
3937      if ( isset( $data ) ) {
3938          if ( is_wp_error( $data ) ) {
3939              $result = array();
3940              foreach ( $data->errors as $code => $messages ) {
3941                  foreach ( $messages as $message ) {
3942                      $result[] = array(
3943                          'code'    => $code,
3944                          'message' => $message,
3945                      );
3946                  }
3947              }
3948  
3949              $response['data'] = $result;
3950          } else {
3951              $response['data'] = $data;
3952          }
3953      }
3954  
3955      wp_send_json( $response, $status_code );
3956  }
3957  
3958  /**
3959   * Checks that a JSONP callback is a valid JavaScript callback.
3960   *
3961   * Only allows alphanumeric characters and the dot character in callback
3962   * function names. This helps to mitigate XSS attacks caused by directly
3963   * outputting user input.
3964   *
3965   * @since 4.6.0
3966   *
3967   * @param string $callback Supplied JSONP callback function.
3968   * @return bool True if valid callback, otherwise false.
3969   */
3970  function wp_check_jsonp_callback( $callback ) {
3971      if ( ! is_string( $callback ) ) {
3972          return false;
3973      }
3974  
3975      preg_replace( '/[^\w\.]/', '', $callback, -1, $illegal_char_count );
3976  
3977      return 0 === $illegal_char_count;
3978  }
3979  
3980  /**
3981   * Retrieve the WordPress home page URL.
3982   *
3983   * If the constant named 'WP_HOME' exists, then it will be used and returned
3984   * by the function. This can be used to counter the redirection on your local
3985   * development environment.
3986   *
3987   * @since 2.2.0
3988   * @access private
3989   *
3990   * @see WP_HOME
3991   *
3992   * @param string $url URL for the home location.
3993   * @return string Homepage location.
3994   */
3995  function _config_wp_home( $url = '' ) {
3996      if ( defined( 'WP_HOME' ) ) {
3997          return untrailingslashit( WP_HOME );
3998      }
3999      return $url;
4000  }
4001  
4002  /**
4003   * Retrieve the WordPress site URL.
4004   *
4005   * If the constant named 'WP_SITEURL' is defined, then the value in that
4006   * constant will always be returned. This can be used for debugging a site
4007   * on your localhost while not having to change the database to your URL.
4008   *
4009   * @since 2.2.0
4010   * @access private
4011   *
4012   * @see WP_SITEURL
4013   *
4014   * @param string $url URL to set the WordPress site location.
4015   * @return string The WordPress Site URL.
4016   */
4017  function _config_wp_siteurl( $url = '' ) {
4018      if ( defined( 'WP_SITEURL' ) ) {
4019          return untrailingslashit( WP_SITEURL );
4020      }
4021      return $url;
4022  }
4023  
4024  /**
4025   * Delete the fresh site option.
4026   *
4027   * @since 4.7.0
4028   * @access private
4029   */
4030  function _delete_option_fresh_site() {
4031      update_option( 'fresh_site', '0' );
4032  }
4033  
4034  /**
4035   * Set the localized direction for MCE plugin.
4036   *
4037   * Will only set the direction to 'rtl', if the WordPress locale has
4038   * the text direction set to 'rtl'.
4039   *
4040   * Fills in the 'directionality' setting, enables the 'directionality'
4041   * plugin, and adds the 'ltr' button to 'toolbar1', formerly
4042   * 'theme_advanced_buttons1' array keys. These keys are then returned
4043   * in the $mce_init (TinyMCE settings) array.
4044   *
4045   * @since 2.1.0
4046   * @access private
4047   *
4048   * @param array $mce_init MCE settings array.
4049   * @return array Direction set for 'rtl', if needed by locale.
4050   */
4051  function _mce_set_direction( $mce_init ) {
4052      if ( is_rtl() ) {
4053          $mce_init['directionality'] = 'rtl';
4054          $mce_init['rtl_ui']         = true;
4055  
4056          if ( ! empty( $mce_init['plugins'] ) && strpos( $mce_init['plugins'], 'directionality' ) === false ) {
4057              $mce_init['plugins'] .= ',directionality';
4058          }
4059  
4060          if ( ! empty( $mce_init['toolbar1'] ) && ! preg_match( '/\bltr\b/', $mce_init['toolbar1'] ) ) {
4061              $mce_init['toolbar1'] .= ',ltr';
4062          }
4063      }
4064  
4065      return $mce_init;
4066  }
4067  
4068  
4069  /**
4070   * Convert smiley code to the icon graphic file equivalent.
4071   *
4072   * You can turn off smilies, by going to the write setting screen and unchecking
4073   * the box, or by setting 'use_smilies' option to false or removing the option.
4074   *
4075   * Plugins may override the default smiley list by setting the $wpsmiliestrans
4076   * to an array, with the key the code the blogger types in and the value the
4077   * image file.
4078   *
4079   * The $wp_smiliessearch global is for the regular expression and is set each
4080   * time the function is called.
4081   *
4082   * The full list of smilies can be found in the function and won't be listed in
4083   * the description. Probably should create a Codex page for it, so that it is
4084   * available.
4085   *
4086   * @global array $wpsmiliestrans
4087   * @global array $wp_smiliessearch
4088   *
4089   * @since 2.2.0
4090   */
4091  function smilies_init() {
4092      global $wpsmiliestrans, $wp_smiliessearch;
4093  
4094      // don't bother setting up smilies if they are disabled
4095      if ( ! get_option( 'use_smilies' ) ) {
4096          return;
4097      }
4098  
4099      if ( ! isset( $wpsmiliestrans ) ) {
4100          $wpsmiliestrans = array(
4101              ':mrgreen:' => 'mrgreen.png',
4102              ':neutral:' => "\xf0\x9f\x98\x90",
4103              ':twisted:' => "\xf0\x9f\x98\x88",
4104              ':arrow:'   => "\xe2\x9e\xa1",
4105              ':shock:'   => "\xf0\x9f\x98\xaf",
4106              ':smile:'   => "\xf0\x9f\x99\x82",
4107              ':???:'     => "\xf0\x9f\x98\x95",
4108              ':cool:'    => "\xf0\x9f\x98\x8e",
4109              ':evil:'    => "\xf0\x9f\x91\xbf",
4110              ':grin:'    => "\xf0\x9f\x98\x80",
4111              ':idea:'    => "\xf0\x9f\x92\xa1",
4112              ':oops:'    => "\xf0\x9f\x98\xb3",
4113              ':razz:'    => "\xf0\x9f\x98\x9b",
4114              ':roll:'    => "\xf0\x9f\x99\x84",
4115              ':wink:'    => "\xf0\x9f\x98\x89",
4116              ':cry:'     => "\xf0\x9f\x98\xa5",
4117              ':eek:'     => "\xf0\x9f\x98\xae",
4118              ':lol:'     => "\xf0\x9f\x98\x86",
4119              ':mad:'     => "\xf0\x9f\x98\xa1",
4120              ':sad:'     => "\xf0\x9f\x99\x81",
4121              '8-)'       => "\xf0\x9f\x98\x8e",
4122              '8-O'       => "\xf0\x9f\x98\xaf",
4123              ':-('       => "\xf0\x9f\x99\x81",
4124              ':-)'       => "\xf0\x9f\x99\x82",
4125              ':-?'       => "\xf0\x9f\x98\x95",
4126              ':-D'       => "\xf0\x9f\x98\x80",
4127              ':-P'       => "\xf0\x9f\x98\x9b",
4128              ':-o'       => "\xf0\x9f\x98\xae",
4129              ':-x'       => "\xf0\x9f\x98\xa1",
4130              ':-|'       => "\xf0\x9f\x98\x90",
4131              ';-)'       => "\xf0\x9f\x98\x89",
4132              // This one transformation breaks regular text with frequency.
4133              //     '8)' => "\xf0\x9f\x98\x8e",
4134              '8O'        => "\xf0\x9f\x98\xaf",
4135              ':('        => "\xf0\x9f\x99\x81",
4136              ':)'        => "\xf0\x9f\x99\x82",
4137              ':?'        => "\xf0\x9f\x98\x95",
4138              ':D'        => "\xf0\x9f\x98\x80",
4139              ':P'        => "\xf0\x9f\x98\x9b",
4140              ':o'        => "\xf0\x9f\x98\xae",
4141              ':x'        => "\xf0\x9f\x98\xa1",
4142              ':|'        => "\xf0\x9f\x98\x90",
4143              ';)'        => "\xf0\x9f\x98\x89",
4144              ':!:'       => "\xe2\x9d\x97",
4145              ':?:'       => "\xe2\x9d\x93",
4146          );
4147      }
4148  
4149      /**
4150       * Filters all the smilies.
4151       *
4152       * This filter must be added before `smilies_init` is run, as
4153       * it is normally only run once to setup the smilies regex.
4154       *
4155       * @since 4.7.0
4156       *
4157       * @param array $wpsmiliestrans List of the smilies.
4158       */
4159      $wpsmiliestrans = apply_filters( 'smilies', $wpsmiliestrans );
4160  
4161      if ( count( $wpsmiliestrans ) == 0 ) {
4162          return;
4163      }
4164  
4165      /*
4166       * NOTE: we sort the smilies in reverse key order. This is to make sure
4167       * we match the longest possible smilie (:???: vs :?) as the regular
4168       * expression used below is first-match
4169       */
4170      krsort( $wpsmiliestrans );
4171  
4172      $spaces = wp_spaces_regexp();
4173  
4174      // Begin first "subpattern"
4175      $wp_smiliessearch = '/(?<=' . $spaces . '|^)';
4176  
4177      $subchar = '';
4178      foreach ( (array) $wpsmiliestrans as $smiley => $img ) {
4179          $firstchar = substr( $smiley, 0, 1 );
4180          $rest      = substr( $smiley, 1 );
4181  
4182          // new subpattern?
4183          if ( $firstchar != $subchar ) {
4184              if ( $subchar != '' ) {
4185                  $wp_smiliessearch .= ')(?=' . $spaces . '|$)';  // End previous "subpattern"
4186                  $wp_smiliessearch .= '|(?<=' . $spaces . '|^)'; // Begin another "subpattern"
4187              }
4188              $subchar           = $firstchar;
4189              $wp_smiliessearch .= preg_quote( $firstchar, '/' ) . '(?:';
4190          } else {
4191              $wp_smiliessearch .= '|';
4192          }
4193          $wp_smiliessearch .= preg_quote( $rest, '/' );
4194      }
4195  
4196      $wp_smiliessearch .= ')(?=' . $spaces . '|$)/m';
4197  
4198  }
4199  
4200  /**
4201   * Merge user defined arguments into defaults array.
4202   *
4203   * This function is used throughout WordPress to allow for both string or array
4204   * to be merged into another array.
4205   *
4206   * @since 2.2.0
4207   * @since 2.3.0 `$args` can now also be an object.
4208   *
4209   * @param string|array|object $args     Value to merge with $defaults.
4210   * @param array               $defaults Optional. Array that serves as the defaults. Default empty.
4211   * @return array Merged user defined values with defaults.
4212   */
4213  function wp_parse_args( $args, $defaults = '' ) {
4214      if ( is_object( $args ) ) {
4215          $parsed_args = get_object_vars( $args );
4216      } elseif ( is_array( $args ) ) {
4217          $parsed_args =& $args;
4218      } else {
4219          wp_parse_str( $args, $parsed_args );
4220      }
4221  
4222      if ( is_array( $defaults ) ) {
4223          return array_merge( $defaults, $parsed_args );
4224      }
4225      return $parsed_args;
4226  }
4227  
4228  /**
4229   * Cleans up an array, comma- or space-separated list of scalar values.
4230   *
4231   * @since 5.1.0
4232   *
4233   * @param array|string $list List of values.
4234   * @return array Sanitized array of values.
4235   */
4236  function wp_parse_list( $list ) {
4237      if ( ! is_array( $list ) ) {
4238          return preg_split( '/[\s,]+/', $list, -1, PREG_SPLIT_NO_EMPTY );
4239      }
4240  
4241      return $list;
4242  }
4243  
4244  /**
4245   * Clean up an array, comma- or space-separated list of IDs.
4246   *
4247   * @since 3.0.0
4248   *
4249   * @param array|string $list List of ids.
4250   * @return array Sanitized array of IDs.
4251   */
4252  function wp_parse_id_list( $list ) {
4253      $list = wp_parse_list( $list );
4254  
4255      return array_unique( array_map( 'absint', $list ) );
4256  }
4257  
4258  /**
4259   * Clean up an array, comma- or space-separated list of slugs.
4260   *
4261   * @since 4.7.0
4262   *
4263   * @param  array|string $list List of slugs.
4264   * @return array Sanitized array of slugs.
4265   */
4266  function wp_parse_slug_list( $list ) {
4267      $list = wp_parse_list( $list );
4268  
4269      return array_unique( array_map( 'sanitize_title', $list ) );
4270  }
4271  
4272  /**
4273   * Extract a slice of an array, given a list of keys.
4274   *
4275   * @since 3.1.0
4276   *
4277   * @param array $array The original array.
4278   * @param array $keys  The list of keys.
4279   * @return array The array slice.
4280   */
4281  function wp_array_slice_assoc( $array, $keys ) {
4282      $slice = array();
4283      foreach ( $keys as $key ) {
4284          if ( isset( $array[ $key ] ) ) {
4285              $slice[ $key ] = $array[ $key ];
4286          }
4287      }
4288  
4289      return $slice;
4290  }
4291  
4292  /**
4293   * Determines if the variable is a numeric-indexed array.
4294   *
4295   * @since 4.4.0
4296   *
4297   * @param mixed $data Variable to check.
4298   * @return bool Whether the variable is a list.
4299   */
4300  function wp_is_numeric_array( $data ) {
4301      if ( ! is_array( $data ) ) {
4302          return false;
4303      }
4304  
4305      $keys        = array_keys( $data );
4306      $string_keys = array_filter( $keys, 'is_string' );
4307      return count( $string_keys ) === 0;
4308  }
4309  
4310  /**
4311   * Filters a list of objects, based on a set of key => value arguments.
4312   *
4313   * @since 3.0.0
4314   * @since 4.7.0 Uses `WP_List_Util` class.
4315   *
4316   * @param array       $list     An array of objects to filter
4317   * @param array       $args     Optional. An array of key => value arguments to match
4318   *                              against each object. Default empty array.
4319   * @param string      $operator Optional. The logical operation to perform. 'or' means
4320   *                              only one element from the array needs to match; 'and'
4321   *                              means all elements must match; 'not' means no elements may
4322   *                              match. Default 'and'.
4323   * @param bool|string $field    A field from the object to place instead of the entire object.
4324   *                              Default false.
4325   * @return array A list of objects or object fields.
4326   */
4327  function wp_filter_object_list( $list, $args = array(), $operator = 'and', $field = false ) {
4328      if ( ! is_array( $list ) ) {
4329          return array();
4330      }
4331  
4332      $util = new WP_List_Util( $list );
4333  
4334      $util->filter( $args, $operator );
4335  
4336      if ( $field ) {
4337          $util->pluck( $field );
4338      }
4339  
4340      return $util->get_output();
4341  }
4342  
4343  /**
4344   * Filters a list of objects, based on a set of key => value arguments.
4345   *
4346   * @since 3.1.0
4347   * @since 4.7.0 Uses `WP_List_Util` class.
4348   *
4349   * @param array  $list     An array of objects to filter.
4350   * @param array  $args     Optional. An array of key => value arguments to match
4351   *                         against each object. Default empty array.
4352   * @param string $operator Optional. The logical operation to perform. 'AND' means
4353   *                         all elements from the array must match. 'OR' means only
4354   *                         one element needs to match. 'NOT' means no elements may
4355   *                         match. Default 'AND'.
4356   * @return array Array of found values.
4357   */
4358  function wp_list_filter( $list, $args = array(), $operator = 'AND' ) {
4359      if ( ! is_array( $list ) ) {
4360          return array();
4361      }
4362  
4363      $util = new WP_List_Util( $list );
4364      return $util->filter( $args, $operator );
4365  }
4366  
4367  /**
4368   * Pluck a certain field out of each object in a list.
4369   *
4370   * This has the same functionality and prototype of
4371   * array_column() (PHP 5.5) but also supports objects.
4372   *
4373   * @since 3.1.0
4374   * @since 4.0.0 $index_key parameter added.
4375   * @since 4.7.0 Uses `WP_List_Util` class.
4376   *
4377   * @param array      $list      List of objects or arrays
4378   * @param int|string $field     Field from the object to place instead of the entire object
4379   * @param int|string $index_key Optional. Field from the object to use as keys for the new array.
4380   *                              Default null.
4381   * @return array Array of found values. If `$index_key` is set, an array of found values with keys
4382   *               corresponding to `$index_key`. If `$index_key` is null, array keys from the original
4383   *               `$list` will be preserved in the results.
4384   */
4385  function wp_list_pluck( $list, $field, $index_key = null ) {
4386      $util = new WP_List_Util( $list );
4387      return $util->pluck( $field, $index_key );
4388  }
4389  
4390  /**
4391   * Sorts a list of objects, based on one or more orderby arguments.
4392   *
4393   * @since 4.7.0
4394   *
4395   * @param array        $list          An array of objects to sort.
4396   * @param string|array $orderby       Optional. Either the field name to order by or an array
4397   *                                    of multiple orderby fields as $orderby => $order.
4398   * @param string       $order         Optional. Either 'ASC' or 'DESC'. Only used if $orderby
4399   *                                    is a string.
4400   * @param bool         $preserve_keys Optional. Whether to preserve keys. Default false.
4401   * @return array The sorted array.
4402   */
4403  function wp_list_sort( $list, $orderby = array(), $order = 'ASC', $preserve_keys = false ) {
4404      if ( ! is_array( $list ) ) {
4405          return array();
4406      }
4407  
4408      $util = new WP_List_Util( $list );
4409      return $util->sort( $orderby, $order, $preserve_keys );
4410  }
4411  
4412  /**
4413   * Determines if Widgets library should be loaded.
4414   *
4415   * Checks to make sure that the widgets library hasn't already been loaded.
4416   * If it hasn't, then it will load the widgets library and run an action hook.
4417   *
4418   * @since 2.2.0
4419   */
4420  function wp_maybe_load_widgets() {
4421      /**
4422       * Filters whether to load the Widgets library.
4423       *
4424       * Passing a falsey value to the filter will effectively short-circuit
4425       * the Widgets library from loading.
4426       *
4427       * @since 2.8.0
4428       *
4429       * @param bool $wp_maybe_load_widgets Whether to load the Widgets library.
4430       *                                    Default true.
4431       */
4432      if ( ! apply_filters( 'load_default_widgets', true ) ) {
4433          return;
4434      }
4435  
4436      require_once ( ABSPATH . WPINC . '/default-widgets.php' );
4437  
4438      add_action( '_admin_menu', 'wp_widgets_add_menu' );
4439  }
4440  
4441  /**
4442   * Append the Widgets menu to the themes main menu.
4443   *
4444   * @since 2.2.0
4445   *
4446   * @global array $submenu
4447   */
4448  function wp_widgets_add_menu() {
4449      global $submenu;
4450  
4451      if ( ! current_theme_supports( 'widgets' ) ) {
4452          return;
4453      }
4454  
4455      $submenu['themes.php'][7] = array( __( 'Widgets' ), 'edit_theme_options', 'widgets.php' );
4456      ksort( $submenu['themes.php'], SORT_NUMERIC );
4457  }
4458  
4459  /**
4460   * Flush all output buffers for PHP 5.2.
4461   *
4462   * Make sure all output buffers are flushed before our singletons are destroyed.
4463   *
4464   * @since 2.2.0
4465   */
4466  function wp_ob_end_flush_all() {
4467      $levels = ob_get_level();
4468      for ( $i = 0; $i < $levels; $i++ ) {
4469          ob_end_flush();
4470      }
4471  }
4472  
4473  /**
4474   * Load custom DB error or display WordPress DB error.
4475   *
4476   * If a file exists in the wp-content directory named db-error.php, then it will
4477   * be loaded instead of displaying the WordPress DB error. If it is not found,
4478   * then the WordPress DB error will be displayed instead.
4479   *
4480   * The WordPress DB error sets the HTTP status header to 500 to try to prevent
4481   * search engines from caching the message. Custom DB messages should do the
4482   * same.
4483   *
4484   * This function was backported to WordPress 2.3.2, but originally was added
4485   * in WordPress 2.5.0.
4486   *
4487   * @since 2.3.2
4488   *
4489   * @global wpdb $wpdb WordPress database abstraction object.
4490   */
4491  function dead_db() {
4492      global $wpdb;
4493  
4494      wp_load_translations_early();
4495  
4496      // Load custom DB error template, if present.
4497      if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
4498          require_once( WP_CONTENT_DIR . '/db-error.php' );
4499          die();
4500      }
4501  
4502      // If installing or in the admin, provide the verbose message.
4503      if ( wp_installing() || defined( 'WP_ADMIN' ) ) {
4504          wp_die( $wpdb->error );
4505      }
4506  
4507      // Otherwise, be terse.
4508      wp_die( '<h1>' . __( 'Error establishing a database connection' ) . '</h1>', __( 'Database Error' ) );
4509  }
4510  
4511  /**
4512   * Convert a value to non-negative integer.
4513   *
4514   * @since 2.5.0
4515   *
4516   * @param mixed $maybeint Data you wish to have converted to a non-negative integer.
4517   * @return int A non-negative integer.
4518   */
4519  function absint( $maybeint ) {
4520      return abs( intval( $maybeint ) );
4521  }
4522  
4523  /**
4524   * Mark a function as deprecated and inform when it has been used.
4525   *
4526   * There is a {@see 'hook deprecated_function_run'} that will be called that can be used
4527   * to get the backtrace up to what file and function called the deprecated
4528   * function.
4529   *
4530   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4531   *
4532   * This function is to be used in every function that is deprecated.
4533   *
4534   * @since 2.5.0
4535   * @access private
4536   *
4537   * @param string $function    The function that was called.
4538   * @param string $version     The version of WordPress that deprecated the function.
4539   * @param string $replacement Optional. The function that should have been called. Default null.
4540   */
4541  function _deprecated_function( $function, $version, $replacement = null ) {
4542  
4543      /**
4544       * Fires when a deprecated function is called.
4545       *
4546       * @since 2.5.0
4547       *
4548       * @param string $function    The function that was called.
4549       * @param string $replacement The function that should have been called.
4550       * @param string $version     The version of WordPress that deprecated the function.
4551       */
4552      do_action( 'deprecated_function_run', $function, $replacement, $version );
4553  
4554      /**
4555       * Filters whether to trigger an error for deprecated functions.
4556       *
4557       * @since 2.5.0
4558       *
4559       * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
4560       */
4561      if ( WP_DEBUG && apply_filters( 'deprecated_function_trigger_error', true ) ) {
4562          if ( function_exists( '__' ) ) {
4563              if ( ! is_null( $replacement ) ) {
4564                  /* translators: 1: PHP function name, 2: Version number, 3: Alternative function name. */
4565                  trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ), $function, $version, $replacement ) );
4566              } else {
4567                  /* translators: 1: PHP function name, 2: Version number. */
4568                  trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ), $function, $version ) );
4569              }
4570          } else {
4571              if ( ! is_null( $replacement ) ) {
4572                  trigger_error( sprintf( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.', $function, $version, $replacement ) );
4573              } else {
4574                  trigger_error( sprintf( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.', $function, $version ) );
4575              }
4576          }
4577      }
4578  }
4579  
4580  /**
4581   * Marks a constructor as deprecated and informs when it has been used.
4582   *
4583   * Similar to _deprecated_function(), but with different strings. Used to
4584   * remove PHP4 style constructors.
4585   *
4586   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4587   *
4588   * This function is to be used in every PHP4 style constructor method that is deprecated.
4589   *
4590   * @since 4.3.0
4591   * @since 4.5.0 Added the `$parent_class` parameter.
4592   *
4593   * @access private
4594   *
4595   * @param string $class        The class containing the deprecated constructor.
4596   * @param string $version      The version of WordPress that deprecated the function.
4597   * @param string $parent_class Optional. The parent class calling the deprecated constructor.
4598   *                             Default empty string.
4599   */
4600  function _deprecated_constructor( $class, $version, $parent_class = '' ) {
4601  
4602      /**
4603       * Fires when a deprecated constructor is called.
4604       *
4605       * @since 4.3.0
4606       * @since 4.5.0 Added the `$parent_class` parameter.
4607       *
4608       * @param string $class        The class containing the deprecated constructor.
4609       * @param string $version      The version of WordPress that deprecated the function.
4610       * @param string $parent_class The parent class calling the deprecated constructor.
4611       */
4612      do_action( 'deprecated_constructor_run', $class, $version, $parent_class );
4613  
4614      /**
4615       * Filters whether to trigger an error for deprecated functions.
4616       *
4617       * `WP_DEBUG` must be true in addition to the filter evaluating to true.
4618       *
4619       * @since 4.3.0
4620       *
4621       * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
4622       */
4623      if ( WP_DEBUG && apply_filters( 'deprecated_constructor_trigger_error', true ) ) {
4624          if ( function_exists( '__' ) ) {
4625              if ( ! empty( $parent_class ) ) {
4626                  trigger_error(
4627                      sprintf(
4628                          /* translators: 1: PHP class name, 2: PHP parent class name, 3: Version number, 4: __construct() method. */
4629                          __( 'The called constructor method for %1$s in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.' ),
4630                          $class,
4631                          $parent_class,
4632                          $version,
4633                          '<pre>__construct()</pre>'
4634                      )
4635                  );
4636              } else {
4637                  trigger_error(
4638                      sprintf(
4639                          /* translators: 1: PHP class name, 2: Version number, 3: __construct() method. */
4640                          __( 'The called constructor method for %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
4641                          $class,
4642                          $version,
4643                          '<pre>__construct()</pre>'
4644                      )
4645                  );
4646              }
4647          } else {
4648              if ( ! empty( $parent_class ) ) {
4649                  trigger_error(
4650                      sprintf(
4651                          'The called constructor method for %1$s in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.',
4652                          $class,
4653                          $parent_class,
4654                          $version,
4655                          '<pre>__construct()</pre>'
4656                      )
4657                  );
4658              } else {
4659                  trigger_error(
4660                      sprintf(
4661                          'The called constructor method for %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
4662                          $class,
4663                          $version,
4664                          '<pre>__construct()</pre>'
4665                      )
4666                  );
4667              }
4668          }
4669      }
4670  
4671  }
4672  
4673  /**
4674   * Mark a file as deprecated and inform when it has been used.
4675   *
4676   * There is a hook {@see 'deprecated_file_included'} that will be called that can be used
4677   * to get the backtrace up to what file and function included the deprecated
4678   * file.
4679   *
4680   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4681   *
4682   * This function is to be used in every file that is deprecated.
4683   *
4684   * @since 2.5.0
4685   * @access private
4686   *
4687   * @param string $file        The file that was included.
4688   * @param string $version     The version of WordPress that deprecated the file.
4689   * @param string $replacement Optional. The file that should have been included based on ABSPATH.
4690   *                            Default null.
4691   * @param string $message     Optional. A message regarding the change. Default empty.
4692   */
4693  function _deprecated_file( $file, $version, $replacement = null, $message = '' ) {
4694  
4695      /**
4696       * Fires when a deprecated file is called.
4697       *
4698       * @since 2.5.0
4699       *
4700       * @param string $file        The file that was called.
4701       * @param string $replacement The file that should have been included based on ABSPATH.
4702       * @param string $version     The version of WordPress that deprecated the file.
4703       * @param string $message     A message regarding the change.
4704       */
4705      do_action( 'deprecated_file_included', $file, $replacement, $version, $message );
4706  
4707      /**
4708       * Filters whether to trigger an error for deprecated files.
4709       *
4710       * @since 2.5.0
4711       *
4712       * @param bool $trigger Whether to trigger the error for deprecated files. Default true.
4713       */
4714      if ( WP_DEBUG && apply_filters( 'deprecated_file_trigger_error', true ) ) {
4715          $message = empty( $message ) ? '' : ' ' . $message;
4716          if ( function_exists( '__' ) ) {
4717              if ( ! is_null( $replacement ) ) {
4718                  /* translators: 1: PHP file name, 2: Version number, 3: Alternative file name. */
4719                  trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ), $file, $version, $replacement ) . $message );
4720              } else {
4721                  /* translators: 1: PHP file name, 2: Version number. */
4722                  trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ), $file, $version ) . $message );
4723              }
4724          } else {
4725              if ( ! is_null( $replacement ) ) {
4726                  trigger_error( sprintf( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.', $file, $version, $replacement ) . $message );
4727              } else {
4728                  trigger_error( sprintf( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.', $file, $version ) . $message );
4729              }
4730          }
4731      }
4732  }
4733  /**
4734   * Mark a function argument as deprecated and inform when it has been used.
4735   *
4736   * This function is to be used whenever a deprecated function argument is used.
4737   * Before this function is called, the argument must be checked for whether it was
4738   * used by comparing it to its default value or evaluating whether it is empty.
4739   * For example:
4740   *
4741   *     if ( ! empty( $deprecated ) ) {
4742   *         _deprecated_argument( __FUNCTION__, '3.0.0' );
4743   *     }
4744   *
4745   * There is a hook deprecated_argument_run that will be called that can be used
4746   * to get the backtrace up to what file and function used the deprecated
4747   * argument.
4748   *
4749   * The current behavior is to trigger a user error if WP_DEBUG is true.
4750   *
4751   * @since 3.0.0
4752   * @access private
4753   *
4754   * @param string $function The function that was called.
4755   * @param string $version  The version of WordPress that deprecated the argument used.
4756   * @param string $message  Optional. A message regarding the change. Default null.
4757   */
4758  function _deprecated_argument( $function, $version, $message = null ) {
4759  
4760      /**
4761       * Fires when a deprecated argument is called.
4762       *
4763       * @since 3.0.0
4764       *
4765       * @param string $function The function that was called.
4766       * @param string $message  A message regarding the change.
4767       * @param string $version  The version of WordPress that deprecated the argument used.
4768       */
4769      do_action( 'deprecated_argument_run', $function, $message, $version );
4770  
4771      /**
4772       * Filters whether to trigger an error for deprecated arguments.
4773       *
4774       * @since 3.0.0
4775       *
4776       * @param bool $trigger Whether to trigger the error for deprecated arguments. Default true.
4777       */
4778      if ( WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) {
4779          if ( function_exists( '__' ) ) {
4780              if ( ! is_null( $message ) ) {
4781                  /* translators: 1: PHP function name, 2: Version number, 3: Optional message regarding the change. */
4782                  trigger_error( sprintf( __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s' ), $function, $version, $message ) );
4783              } else {
4784                  /* translators: 1: PHP function name, 2: Version number. */
4785                  trigger_error( sprintf( __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.' ), $function, $version ) );
4786              }
4787          } else {
4788              if ( ! is_null( $message ) ) {
4789                  trigger_error( sprintf( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s', $function, $version, $message ) );
4790              } else {
4791                  trigger_error( sprintf( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.', $function, $version ) );
4792              }
4793          }
4794      }
4795  }
4796  
4797  /**
4798   * Marks a deprecated action or filter hook as deprecated and throws a notice.
4799   *
4800   * Use the {@see 'deprecated_hook_run'} action to get the backtrace describing where
4801   * the deprecated hook was called.
4802   *
4803   * Default behavior is to trigger a user error if `WP_DEBUG` is true.
4804   *
4805   * This function is called by the do_action_deprecated() and apply_filters_deprecated()
4806   * functions, and so generally does not need to be called directly.
4807   *
4808   * @since 4.6.0
4809   * @access private
4810   *
4811   * @param string $hook        The hook that was used.
4812   * @param string $version     The version of WordPress that deprecated the hook.
4813   * @param string $replacement Optional. The hook that should have been used.
4814   * @param string $message     Optional. A message regarding the change.
4815   */
4816  function _deprecated_hook( $hook, $version, $replacement = null, $message = null ) {
4817      /**
4818       * Fires when a deprecated hook is called.
4819       *
4820       * @since 4.6.0
4821       *
4822       * @param string $hook        The hook that was called.
4823       * @param string $replacement The hook that should be used as a replacement.
4824       * @param string $version     The version of WordPress that deprecated the argument used.
4825       * @param string $message     A message regarding the change.
4826       */
4827      do_action( 'deprecated_hook_run', $hook, $replacement, $version, $message );
4828  
4829      /**
4830       * Filters whether to trigger deprecated hook errors.
4831       *
4832       * @since 4.6.0
4833       *
4834       * @param bool $trigger Whether to trigger deprecated hook errors. Requires
4835       *                      `WP_DEBUG` to be defined true.
4836       */
4837      if ( WP_DEBUG && apply_filters( 'deprecated_hook_trigger_error', true ) ) {
4838          $message = empty( $message ) ? '' : ' ' . $message;
4839          if ( ! is_null( $replacement ) ) {
4840              /* translators: 1: WordPress hook name, 2: Version number, 3: Alternative hook name. */
4841              trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ), $hook, $version, $replacement ) . $message );
4842          } else {
4843              /* translators: 1: WordPress hook name, 2: Version number. */
4844              trigger_error( sprintf( __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ), $hook, $version ) . $message );
4845          }
4846      }
4847  }
4848  
4849  /**
4850   * Mark something as being incorrectly called.
4851   *
4852   * There is a hook {@see 'doing_it_wrong_run'} that will be called that can be used
4853   * to get the backtrace up to what file and function called the deprecated
4854   * function.
4855   *
4856   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
4857   *
4858   * @since 3.1.0
4859   * @access private
4860   *
4861   * @param string $function The function that was called.
4862   * @param string $message  A message explaining what has been done incorrectly.
4863   * @param string $version  The version of WordPress where the message was added.
4864   */
4865  function _doing_it_wrong( $function, $message, $version ) {
4866  
4867      /**
4868       * Fires when the given function is being used incorrectly.
4869       *
4870       * @since 3.1.0
4871       *
4872       * @param string $function The function that was called.
4873       * @param string $message  A message explaining what has been done incorrectly.
4874       * @param string $version  The version of WordPress where the message was added.
4875       */
4876      do_action( 'doing_it_wrong_run', $function, $message, $version );
4877  
4878      /**
4879       * Filters whether to trigger an error for _doing_it_wrong() calls.
4880       *
4881       * @since 3.1.0
4882       * @since 5.1.0 Added the $function, $message and $version parameters.
4883       *
4884       * @param bool   $trigger  Whether to trigger the error for _doing_it_wrong() calls. Default true.
4885       * @param string $function The function that was called.
4886       * @param string $message  A message explaining what has been done incorrectly.
4887       * @param string $version  The version of WordPress where the message was added.
4888       */
4889      if ( WP_DEBUG && apply_filters( 'doing_it_wrong_trigger_error', true, $function, $message, $version ) ) {
4890          if ( function_exists( '__' ) ) {
4891              if ( is_null( $version ) ) {
4892                  $version = '';
4893              } else {
4894                  /* translators: %s: Version number. */
4895                  $version = sprintf( __( '(This message was added in version %s.)' ), $version );
4896              }
4897              $message .= ' ' . sprintf(
4898                  /* translators: %s: Documentation URL. */
4899                  __( 'Please see <a href="%s">Debugging in WordPress</a> for more information.' ),
4900                  __( 'https://wordpress.org/support/article/debugging-in-wordpress/' )
4901              );
4902              /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: Version information message. */
4903              trigger_error( sprintf( __( '%1$s was called <strong>incorrectly</strong>. %2$s %3$s' ), $function, $message, $version ) );
4904          } else {
4905              if ( is_null( $version ) ) {
4906                  $version = '';
4907              } else {
4908                  $version = sprintf( '(This message was added in version %s.)', $version );
4909              }
4910              $message .= sprintf(
4911                  ' Please see <a href="%s">Debugging in WordPress</a> for more information.',
4912                  'https://wordpress.org/support/article/debugging-in-wordpress/'
4913              );
4914              trigger_error( sprintf( '%1$s was called <strong>incorrectly</strong>. %2$s %3$s', $function, $message, $version ) );
4915          }
4916      }
4917  }
4918  
4919  /**
4920   * Is the server running earlier than 1.5.0 version of lighttpd?
4921   *
4922   * @since 2.5.0
4923   *
4924   * @return bool Whether the server is running lighttpd < 1.5.0.
4925   */
4926  function is_lighttpd_before_150() {
4927      $server_parts    = explode( '/', isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '' );
4928      $server_parts[1] = isset( $server_parts[1] ) ? $server_parts[1] : '';
4929      return  'lighttpd' == $server_parts[0] && -1 == version_compare( $server_parts[1], '1.5.0' );
4930  }
4931  
4932  /**
4933   * Does the specified module exist in the Apache config?
4934   *
4935   * @since 2.5.0
4936   *
4937   * @global bool $is_apache
4938   *
4939   * @param string $mod     The module, e.g. mod_rewrite.
4940   * @param bool   $default Optional. The default return value if the module is not found. Default false.
4941   * @return bool Whether the specified module is loaded.
4942   */
4943  function apache_mod_loaded( $mod, $default = false ) {
4944      global $is_apache;
4945  
4946      if ( ! $is_apache ) {
4947          return false;
4948      }
4949  
4950      if ( function_exists( 'apache_get_modules' ) ) {
4951          $mods = apache_get_modules();
4952          if ( in_array( $mod, $mods ) ) {
4953              return true;
4954          }
4955      } elseif ( function_exists( 'phpinfo' ) && false === strpos( ini_get( 'disable_functions' ), 'phpinfo' ) ) {
4956              ob_start();
4957              phpinfo( 8 );
4958              $phpinfo = ob_get_clean();
4959          if ( false !== strpos( $phpinfo, $mod ) ) {
4960              return true;
4961          }
4962      }
4963      return $default;
4964  }
4965  
4966  /**
4967   * Check if IIS 7+ supports pretty permalinks.
4968   *
4969   * @since 2.8.0
4970   *
4971   * @global bool $is_iis7
4972   *
4973   * @return bool Whether IIS7 supports permalinks.
4974   */
4975  function iis7_supports_permalinks() {
4976      global $is_iis7;
4977  
4978      $supports_permalinks = false;
4979      if ( $is_iis7 ) {
4980          /* First we check if the DOMDocument class exists. If it does not exist, then we cannot
4981           * easily update the xml configuration file, hence we just bail out and tell user that
4982           * pretty permalinks cannot be used.
4983           *
4984           * Next we check if the URL Rewrite Module 1.1 is loaded and enabled for the web site. When
4985           * URL Rewrite 1.1 is loaded it always sets a server variable called 'IIS_UrlRewriteModule'.
4986           * Lastly we make sure that PHP is running via FastCGI. This is important because if it runs
4987           * via ISAPI then pretty permalinks will not work.
4988           */
4989          $supports_permalinks = class_exists( 'DOMDocument', false ) && isset( $_SERVER['IIS_UrlRewriteModule'] ) && ( PHP_SAPI == 'cgi-fcgi' );
4990      }
4991  
4992      /**
4993       * Filters whether IIS 7+ supports pretty permalinks.
4994       *
4995       * @since 2.8.0
4996       *
4997       * @param bool $supports_permalinks Whether IIS7 supports permalinks. Default false.
4998       */
4999      return apply_filters( 'iis7_supports_permalinks', $supports_permalinks );
5000  }
5001  
5002  /**
5003   * Validates a file name and path against an allowed set of rules.
5004   *
5005   * A return value of `1` means the file path contains directory traversal.
5006   *
5007   * A return value of `2` means the file path contains a Windows drive path.
5008   *
5009   * A return value of `3` means the file is not in the allowed files list.
5010   *
5011   * @since 1.2.0
5012   *
5013   * @param string $file          File path.
5014   * @param array  $allowed_files Optional. List of allowed files.
5015   * @return int 0 means nothing is wrong, greater than 0 means something was wrong.
5016   */
5017  function validate_file( $file, $allowed_files = array() ) {
5018      // `../` on its own is not allowed:
5019      if ( '../' === $file ) {
5020          return 1;
5021      }
5022  
5023      // More than one occurence of `../` is not allowed:
5024      if ( preg_match_all( '#\.\./#', $file, $matches, PREG_SET_ORDER ) && ( count( $matches ) > 1 ) ) {
5025          return 1;
5026      }
5027  
5028      // `../` which does not occur at the end of the path is not allowed:
5029      if ( false !== strpos( $file, '../' ) && '../' !== mb_substr( $file, -3, 3 ) ) {
5030          return 1;
5031      }
5032  
5033      // Files not in the allowed file list are not allowed:
5034      if ( ! empty( $allowed_files ) && ! in_array( $file, $allowed_files ) ) {
5035          return 3;
5036      }
5037  
5038      // Absolute Windows drive paths are not allowed:
5039      if ( ':' == substr( $file, 1, 1 ) ) {
5040          return 2;
5041      }
5042  
5043      return 0;
5044  }
5045  
5046  /**
5047   * Whether to force SSL used for the Administration Screens.
5048   *
5049   * @since 2.6.0
5050   *
5051   * @staticvar bool $forced
5052   *
5053   * @param string|bool $force Optional. Whether to force SSL in admin screens. Default null.
5054   * @return bool True if forced, false if not forced.
5055   */
5056  function force_ssl_admin( $force = null ) {
5057      static $forced = false;
5058  
5059      if ( ! is_null( $force ) ) {
5060          $old_forced = $forced;
5061          $forced     = $force;
5062          return $old_forced;
5063      }
5064  
5065      return $forced;
5066  }
5067  
5068  /**
5069   * Guess the URL for the site.
5070   *
5071   * Will remove wp-admin links to retrieve only return URLs not in the wp-admin
5072   * directory.
5073   *
5074   * @since 2.6.0
5075   *
5076   * @return string The guessed URL.
5077   */
5078  function wp_guess_url() {
5079      if ( defined( 'WP_SITEURL' ) && '' != WP_SITEURL ) {
5080          $url = WP_SITEURL;
5081      } else {
5082          $abspath_fix         = str_replace( '\\', '/', ABSPATH );
5083          $script_filename_dir = dirname( $_SERVER['SCRIPT_FILENAME'] );
5084  
5085          // The request is for the admin
5086          if ( strpos( $_SERVER['REQUEST_URI'], 'wp-admin' ) !== false || strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) !== false ) {
5087              $path = preg_replace( '#/(wp-admin/.*|wp-login.php)#i', '', $_SERVER['REQUEST_URI'] );
5088  
5089              // The request is for a file in ABSPATH
5090          } elseif ( $script_filename_dir . '/' == $abspath_fix ) {
5091              // Strip off any file/query params in the path
5092              $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['PHP_SELF'] );
5093  
5094          } else {
5095              if ( false !== strpos( $_SERVER['SCRIPT_FILENAME'], $abspath_fix ) ) {
5096                  // Request is hitting a file inside ABSPATH
5097                  $directory = str_replace( ABSPATH, '', $script_filename_dir );
5098                  // Strip off the sub directory, and any file/query params
5099                  $path = preg_replace( '#/' . preg_quote( $directory, '#' ) . '/[^/]*$#i', '', $_SERVER['REQUEST_URI'] );
5100              } elseif ( false !== strpos( $abspath_fix, $script_filename_dir ) ) {
5101                  // Request is hitting a file above ABSPATH
5102                  $subdirectory = substr( $abspath_fix, strpos( $abspath_fix, $script_filename_dir ) + strlen( $script_filename_dir ) );
5103                  // Strip off any file/query params from the path, appending the sub directory to the installation
5104                  $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['REQUEST_URI'] ) . $subdirectory;
5105              } else {
5106                  $path = $_SERVER['REQUEST_URI'];
5107              }
5108          }
5109  
5110          $schema = is_ssl() ? 'https://' : 'http://'; // set_url_scheme() is not defined yet
5111          $url    = $schema . $_SERVER['HTTP_HOST'] . $path;
5112      }
5113  
5114      return rtrim( $url, '/' );
5115  }
5116  
5117  /**
5118   * Temporarily suspend cache additions.
5119   *
5120   * Stops more data being added to the cache, but still allows cache retrieval.
5121   * This is useful for actions, such as imports, when a lot of data would otherwise
5122   * be almost uselessly added to the cache.
5123   *
5124   * Suspension lasts for a single page load at most. Remember to call this
5125   * function again if you wish to re-enable cache adds earlier.
5126   *
5127   * @since 3.3.0
5128   *
5129   * @staticvar bool $_suspend
5130   *
5131   * @param bool $suspend Optional. Suspends additions if true, re-enables them if false.
5132   * @return bool The current suspend setting
5133   */
5134  function wp_suspend_cache_addition( $suspend = null ) {
5135      static $_suspend = false;
5136  
5137      if ( is_bool( $suspend ) ) {
5138          $_suspend = $suspend;
5139      }
5140  
5141      return $_suspend;
5142  }
5143  
5144  /**
5145   * Suspend cache invalidation.
5146   *
5147   * Turns cache invalidation on and off. Useful during imports where you don't want to do
5148   * invalidations every time a post is inserted. Callers must be sure that what they are
5149   * doing won't lead to an inconsistent cache when invalidation is suspended.
5150   *
5151   * @since 2.7.0
5152   *
5153   * @global bool $_wp_suspend_cache_invalidation
5154   *
5155   * @param bool $suspend Optional. Whether to suspend or enable cache invalidation. Default true.
5156   * @return bool The current suspend setting.
5157   */
5158  function wp_suspend_cache_invalidation( $suspend = true ) {
5159      global $_wp_suspend_cache_invalidation;
5160  
5161      $current_suspend                = $_wp_suspend_cache_invalidation;
5162      $_wp_suspend_cache_invalidation = $suspend;
5163      return $current_suspend;
5164  }
5165  
5166  /**
5167   * Determine whether a site is the main site of the current network.
5168   *
5169   * @since 3.0.0
5170   * @since 4.9.0 The `$network_id` parameter was added.
5171   *
5172   * @param int $site_id    Optional. Site ID to test. Defaults to current site.
5173   * @param int $network_id Optional. Network ID of the network to check for.
5174   *                        Defaults to current network.
5175   * @return bool True if $site_id is the main site of the network, or if not
5176   *              running Multisite.
5177   */
5178  function is_main_site( $site_id = null, $network_id = null ) {
5179      if ( ! is_multisite() ) {
5180          return true;
5181      }
5182  
5183      if ( ! $site_id ) {
5184          $site_id = get_current_blog_id();
5185      }
5186  
5187      $site_id = (int) $site_id;
5188  
5189      return $site_id === get_main_site_id( $network_id );
5190  }
5191  
5192  /**
5193   * Gets the main site ID.
5194   *
5195   * @since 4.9.0
5196   *
5197   * @param int $network_id Optional. The ID of the network for which to get the main site.
5198   *                        Defaults to the current network.
5199   * @return int The ID of the main site.
5200   */
5201  function get_main_site_id( $network_id = null ) {
5202      if ( ! is_multisite() ) {
5203          return get_current_blog_id();
5204      }
5205  
5206      $network = get_network( $network_id );
5207      if ( ! $network ) {
5208          return 0;
5209      }
5210  
5211      return $network->site_id;
5212  }
5213  
5214  /**
5215   * Determine whether a network is the main network of the Multisite installation.
5216   *
5217   * @since 3.7.0
5218   *
5219   * @param int $network_id Optional. Network ID to test. Defaults to current network.
5220   * @return bool True if $network_id is the main network, or if not running Multisite.
5221   */
5222  function is_main_network( $network_id = null ) {
5223      if ( ! is_multisite() ) {
5224          return true;
5225      }
5226  
5227      if ( null === $network_id ) {
5228          $network_id = get_current_network_id();
5229      }
5230  
5231      $network_id = (int) $network_id;
5232  
5233      return ( $network_id === get_main_network_id() );
5234  }
5235  
5236  /**
5237   * Get the main network ID.
5238   *
5239   * @since 4.3.0
5240   *
5241   * @return int The ID of the main network.
5242   */
5243  function get_main_network_id() {
5244      if ( ! is_multisite() ) {
5245          return 1;
5246      }
5247  
5248      $current_network = get_network();
5249  
5250      if ( defined( 'PRIMARY_NETWORK_ID' ) ) {
5251          $main_network_id = PRIMARY_NETWORK_ID;
5252      } elseif ( isset( $current_network->id ) && 1 === (int) $current_network->id ) {
5253          // If the current network has an ID of 1, assume it is the main network.
5254          $main_network_id = 1;
5255      } else {
5256          $_networks       = get_networks(
5257              array(
5258                  'fields' => 'ids',
5259                  'number' => 1,
5260              )
5261          );
5262          $main_network_id = array_shift( $_networks );
5263      }
5264  
5265      /**
5266       * Filters the main network ID.
5267       *
5268       * @since 4.3.0
5269       *
5270       * @param int $main_network_id The ID of the main network.
5271       */
5272      return (int) apply_filters( 'get_main_network_id', $main_network_id );
5273  }
5274  
5275  /**
5276   * Determine whether global terms are enabled.
5277   *
5278   * @since 3.0.0
5279   *
5280   * @staticvar bool $global_terms
5281   *
5282   * @return bool True if multisite and global terms enabled.
5283   */
5284  function global_terms_enabled() {
5285      if ( ! is_multisite() ) {
5286          return false;
5287      }
5288  
5289      static $global_terms = null;
5290      if ( is_null( $global_terms ) ) {
5291  
5292          /**
5293           * Filters whether global terms are enabled.
5294           *
5295           * Passing a non-null value to the filter will effectively short-circuit the function,
5296           * returning the value of the 'global_terms_enabled' site option instead.
5297           *
5298           * @since 3.0.0
5299           *
5300           * @param null $enabled Whether global terms are enabled.
5301           */
5302          $filter = apply_filters( 'global_terms_enabled', null );
5303          if ( ! is_null( $filter ) ) {
5304              $global_terms = (bool) $filter;
5305          } else {
5306              $global_terms = (bool) get_site_option( 'global_terms_enabled', false );
5307          }
5308      }
5309      return $global_terms;
5310  }
5311  
5312  /**
5313   * Determines whether site meta is enabled.
5314   *
5315   * This function checks whether the 'blogmeta' database table exists. The result is saved as
5316   * a setting for the main network, making it essentially a global setting. Subsequent requests
5317   * will refer to this setting instead of running the query.
5318   *
5319   * @since 5.1.0
5320   *
5321   * @global wpdb $wpdb WordPress database abstraction object.
5322   *
5323   * @return bool True if site meta is supported, false otherwise.
5324   */
5325  function is_site_meta_supported() {
5326      global $wpdb;
5327  
5328      if ( ! is_multisite() ) {
5329          return false;
5330      }
5331  
5332      $network_id = get_main_network_id();
5333  
5334      $supported = get_network_option( $network_id, 'site_meta_supported', false );
5335      if ( false === $supported ) {
5336          $supported = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->blogmeta}'" ) ? 1 : 0;
5337  
5338          update_network_option( $network_id, 'site_meta_supported', $supported );
5339      }
5340  
5341      return (bool) $supported;
5342  }
5343  
5344  /**
5345   * gmt_offset modification for smart timezone handling.
5346   *
5347   * Overrides the gmt_offset option if we have a timezone_string available.
5348   *
5349   * @since 2.8.0
5350   *
5351   * @return float|false Timezone GMT offset, false otherwise.
5352   */
5353  function wp_timezone_override_offset() {
5354      $timezone_string = get_option( 'timezone_string' );
5355      if ( ! $timezone_string ) {
5356          return false;
5357      }
5358  
5359      $timezone_object = timezone_open( $timezone_string );
5360      $datetime_object = date_create();
5361      if ( false === $timezone_object || false === $datetime_object ) {
5362          return false;
5363      }
5364      return round( timezone_offset_get( $timezone_object, $datetime_object ) / HOUR_IN_SECONDS, 2 );
5365  }
5366  
5367  /**
5368   * Sort-helper for timezones.
5369   *
5370   * @since 2.9.0
5371   * @access private
5372   *
5373   * @param array $a
5374   * @param array $b
5375   * @return int
5376   */
5377  function _wp_timezone_choice_usort_callback( $a, $b ) {
5378      // Don't use translated versions of Etc
5379      if ( 'Etc' === $a['continent'] && 'Etc' === $b['continent'] ) {
5380          // Make the order of these more like the old dropdown
5381          if ( 'GMT+' === substr( $a['city'], 0, 4 ) && 'GMT+' === substr( $b['city'], 0, 4 ) ) {
5382              return -1 * ( strnatcasecmp( $a['city'], $b['city'] ) );
5383          }
5384          if ( 'UTC' === $a['city'] ) {
5385              if ( 'GMT+' === substr( $b['city'], 0, 4 ) ) {
5386                  return 1;
5387              }
5388              return -1;
5389          }
5390          if ( 'UTC' === $b['city'] ) {
5391              if ( 'GMT+' === substr( $a['city'], 0, 4 ) ) {
5392                  return -1;
5393              }
5394              return 1;
5395          }
5396          return strnatcasecmp( $a['city'], $b['city'] );
5397      }
5398      if ( $a['t_continent'] == $b['t_continent'] ) {
5399          if ( $a['t_city'] == $b['t_city'] ) {
5400              return strnatcasecmp( $a['t_subcity'], $b['t_subcity'] );
5401          }
5402          return strnatcasecmp( $a['t_city'], $b['t_city'] );
5403      } else {
5404          // Force Etc to the bottom of the list
5405          if ( 'Etc' === $a['continent'] ) {
5406              return 1;
5407          }
5408          if ( 'Etc' === $b['continent'] ) {
5409              return -1;
5410          }
5411          return strnatcasecmp( $a['t_continent'], $b['t_continent'] );
5412      }
5413  }
5414  
5415  /**
5416   * Gives a nicely-formatted list of timezone strings.
5417   *
5418   * @since 2.9.0
5419   * @since 4.7.0 Added the `$locale` parameter.
5420   *
5421   * @staticvar bool $mo_loaded
5422   * @staticvar string $locale_loaded
5423   *
5424   * @param string $selected_zone Selected timezone.
5425   * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
5426   * @return string
5427   */
5428  function wp_timezone_choice( $selected_zone, $locale = null ) {
5429      static $mo_loaded = false, $locale_loaded = null;
5430  
5431      $continents = array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' );
5432  
5433      // Load translations for continents and cities.
5434      if ( ! $mo_loaded || $locale !== $locale_loaded ) {
5435          $locale_loaded = $locale ? $locale : get_locale();
5436          $mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
5437          unload_textdomain( 'continents-cities' );
5438          load_textdomain( 'continents-cities', $mofile );
5439          $mo_loaded = true;
5440      }
5441  
5442      $zonen = array();
5443      foreach ( timezone_identifiers_list() as $zone ) {
5444          $zone = explode( '/', $zone );
5445          if ( ! in_array( $zone[0], $continents ) ) {
5446              continue;
5447          }
5448  
5449          // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later
5450          $exists    = array(
5451              0 => ( isset( $zone[0] ) && $zone[0] ),
5452              1 => ( isset( $zone[1] ) && $zone[1] ),
5453              2 => ( isset( $zone[2] ) && $zone[2] ),
5454          );
5455          $exists[3] = ( $exists[0] && 'Etc' !== $zone[0] );
5456          $exists[4] = ( $exists[1] && $exists[3] );
5457          $exists[5] = ( $exists[2] && $exists[3] );
5458  
5459          // phpcs:disable WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText
5460          $zonen[] = array(
5461              'continent'   => ( $exists[0] ? $zone[0] : '' ),
5462              'city'        => ( $exists[1] ? $zone[1] : '' ),
5463              'subcity'     => ( $exists[2] ? $zone[2] : '' ),
5464              't_continent' => ( $exists[3] ? translate( str_replace( '_', ' ', $zone[0] ), 'continents-cities' ) : '' ),
5465              't_city'      => ( $exists[4] ? translate( str_replace( '_', ' ', $zone[1] ), 'continents-cities' ) : '' ),
5466              't_subcity'   => ( $exists[5] ? translate( str_replace( '_', ' ', $zone[2] ), 'continents-cities' ) : '' ),
5467          );
5468          // phpcs:enable
5469      }
5470      usort( $zonen, '_wp_timezone_choice_usort_callback' );
5471  
5472      $structure = array();
5473  
5474      if ( empty( $selected_zone ) ) {
5475          $structure[] = '<option selected="selected" value="">' . __( 'Select a city' ) . '</option>';
5476      }
5477  
5478      foreach ( $zonen as $key => $zone ) {
5479          // Build value in an array to join later
5480          $value = array( $zone['continent'] );
5481  
5482          if ( empty( $zone['city'] ) ) {
5483              // It's at the continent level (generally won't happen)
5484              $display = $zone['t_continent'];
5485          } else {
5486              // It's inside a continent group
5487  
5488              // Continent optgroup
5489              if ( ! isset( $zonen[ $key - 1 ] ) || $zonen[ $key - 1 ]['continent'] !== $zone['continent'] ) {
5490                  $label       = $zone['t_continent'];
5491                  $structure[] = '<optgroup label="' . esc_attr( $label ) . '">';
5492              }
5493  
5494              // Add the city to the value
5495              $value[] = $zone['city'];
5496  
5497              $display = $zone['t_city'];
5498              if ( ! empty( $zone['subcity'] ) ) {
5499                  // Add the subcity to the value
5500                  $value[]  = $zone['subcity'];
5501                  $display .= ' - ' . $zone['t_subcity'];
5502              }
5503          }
5504  
5505          // Build the value
5506          $value    = join( '/', $value );
5507          $selected = '';
5508          if ( $value === $selected_zone ) {
5509              $selected = 'selected="selected" ';
5510          }
5511          $structure[] = '<option ' . $selected . 'value="' . esc_attr( $value ) . '">' . esc_html( $display ) . '</option>';
5512  
5513          // Close continent optgroup
5514          if ( ! empty( $zone['city'] ) && ( ! isset( $zonen[ $key + 1 ] ) || ( isset( $zonen[ $key + 1 ] ) && $zonen[ $key + 1 ]['continent'] !== $zone['continent'] ) ) ) {
5515              $structure[] = '</optgroup>';
5516          }
5517      }
5518  
5519      // Do UTC
5520      $structure[] = '<optgroup label="' . esc_attr__( 'UTC' ) . '">';
5521      $selected    = '';
5522      if ( 'UTC' === $selected_zone ) {
5523          $selected = 'selected="selected" ';
5524      }
5525      $structure[] = '<option ' . $selected . 'value="' . esc_attr( 'UTC' ) . '">' . __( 'UTC' ) . '</option>';
5526      $structure[] = '</optgroup>';
5527  
5528      // Do manual UTC offsets
5529      $structure[]  = '<optgroup label="' . esc_attr__( 'Manual Offsets' ) . '">';
5530      $offset_range = array(
5531          -12,
5532          -11.5,
5533          -11,
5534          -10.5,
5535          -10,
5536          -9.5,
5537          -9,
5538          -8.5,
5539          -8,
5540          -7.5,
5541          -7,
5542          -6.5,
5543          -6,
5544          -5.5,
5545          -5,
5546          -4.5,
5547          -4,
5548          -3.5,
5549          -3,
5550          -2.5,
5551          -2,
5552          -1.5,
5553          -1,
5554          -0.5,
5555          0,
5556          0.5,
5557          1,
5558          1.5,
5559          2,
5560          2.5,
5561          3,
5562          3.5,
5563          4,
5564          4.5,
5565          5,
5566          5.5,
5567          5.75,
5568          6,
5569          6.5,
5570          7,
5571          7.5,
5572          8,
5573          8.5,
5574          8.75,
5575          9,
5576          9.5,
5577          10,
5578          10.5,
5579          11,
5580          11.5,
5581          12,
5582          12.75,
5583          13,
5584          13.75,
5585          14,
5586      );
5587      foreach ( $offset_range as $offset ) {
5588          if ( 0 <= $offset ) {
5589              $offset_name = '+' . $offset;
5590          } else {
5591              $offset_name = (string) $offset;
5592          }
5593  
5594          $offset_value = $offset_name;
5595          $offset_name  = str_replace( array( '.25', '.5', '.75' ), array( ':15', ':30', ':45' ), $offset_name );
5596          $offset_name  = 'UTC' . $offset_name;
5597          $offset_value = 'UTC' . $offset_value;
5598          $selected     = '';
5599          if ( $offset_value === $selected_zone ) {
5600              $selected = 'selected="selected" ';
5601          }
5602          $structure[] = '<option ' . $selected . 'value="' . esc_attr( $offset_value ) . '">' . esc_html( $offset_name ) . '</option>';
5603  
5604      }
5605      $structure[] = '</optgroup>';
5606  
5607      return join( "\n", $structure );
5608  }
5609  
5610  /**
5611   * Strip close comment and close php tags from file headers used by WP.
5612   *
5613   * @since 2.8.0
5614   * @access private
5615   *
5616   * @see https://core.trac.wordpress.org/ticket/8497
5617   *
5618   * @param string $str Header comment to clean up.
5619   * @return string
5620   */
5621  function _cleanup_header_comment( $str ) {
5622      return trim( preg_replace( '/\s*(?:\*\/|\?>).*/', '', $str ) );
5623  }
5624  
5625  /**
5626   * Permanently delete comments or posts of any type that have held a status
5627   * of 'trash' for the number of days defined in EMPTY_TRASH_DAYS.
5628   *
5629   * The default value of `EMPTY_TRASH_DAYS` is 30 (days).
5630   *
5631   * @since 2.9.0
5632   *
5633   * @global wpdb $wpdb WordPress database abstraction object.
5634   */
5635  function wp_scheduled_delete() {
5636      global $wpdb;
5637  
5638      $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
5639  
5640      $posts_to_delete = $wpdb->get_results( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < %d", $delete_timestamp ), ARRAY_A );
5641  
5642      foreach ( (array) $posts_to_delete as $post ) {
5643          $post_id = (int) $post['post_id'];
5644          if ( ! $post_id ) {
5645              continue;
5646          }
5647  
5648          $del_post = get_post( $post_id );
5649  
5650          if ( ! $del_post || 'trash' != $del_post->post_status ) {
5651              delete_post_meta( $post_id, '_wp_trash_meta_status' );
5652              delete_post_meta( $post_id, '_wp_trash_meta_time' );
5653          } else {
5654              wp_delete_post( $post_id );
5655          }
5656      }
5657  
5658      $comments_to_delete = $wpdb->get_results( $wpdb->prepare( "SELECT comment_id FROM $wpdb->commentmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < %d", $delete_timestamp ), ARRAY_A );
5659  
5660      foreach ( (array) $comments_to_delete as $comment ) {
5661          $comment_id = (int) $comment['comment_id'];
5662          if ( ! $comment_id ) {
5663              continue;
5664          }
5665  
5666          $del_comment = get_comment( $comment_id );
5667  
5668          if ( ! $del_comment || 'trash' != $del_comment->comment_approved ) {
5669              delete_comment_meta( $comment_id, '_wp_trash_meta_time' );
5670              delete_comment_meta( $comment_id, '_wp_trash_meta_status' );
5671          } else {
5672              wp_delete_comment( $del_comment );
5673          }
5674      }
5675  }
5676  
5677  /**
5678   * Retrieve metadata from a file.
5679   *
5680   * Searches for metadata in the first 8 KB of a file, such as a plugin or theme.
5681   * Each piece of metadata must be on its own line. Fields can not span multiple
5682   * lines, the value will get cut at the end of the first line.
5683   *
5684   * If the file data is not within that first 8 KB, then the author should correct
5685   * their plugin file and move the data headers to the top.
5686   *
5687   * @link https://codex.wordpress.org/File_Header
5688   *
5689   * @since 2.9.0
5690   *
5691   * @param string $file            Absolute path to the file.
5692   * @param array  $default_headers List of headers, in the format `array( 'HeaderKey' => 'Header Name' )`.
5693   * @param string $context         Optional. If specified adds filter hook {@see 'extra_$context_headers'}.
5694   *                                Default empty.
5695   * @return array Array of file headers in `HeaderKey => Header Value` format.
5696   */
5697  function get_file_data( $file, $default_headers, $context = '' ) {
5698      // We don't need to write to the file, so just open for reading.
5699      $fp = fopen( $file, 'r' );
5700  
5701      // Pull only the first 8 KB of the file in.
5702      $file_data = fread( $fp, 8 * KB_IN_BYTES );
5703  
5704      // PHP will close file handle, but we are good citizens.
5705      fclose( $fp );
5706  
5707      // Make sure we catch CR-only line endings.
5708      $file_data = str_replace( "\r", "\n", $file_data );
5709  
5710      /**
5711       * Filters extra file headers by context.
5712       *
5713       * The dynamic portion of the hook name, `$context`, refers to
5714       * the context where extra headers might be loaded.
5715       *
5716       * @since 2.9.0
5717       *
5718       * @param array $extra_context_headers Empty array by default.
5719       */
5720      $extra_headers = $context ? apply_filters( "extra_{$context}_headers", array() ) : array();
5721      if ( $extra_headers ) {
5722          $extra_headers = array_combine( $extra_headers, $extra_headers ); // keys equal values
5723          $all_headers   = array_merge( $extra_headers, (array) $default_headers );
5724      } else {
5725          $all_headers = $default_headers;
5726      }
5727  
5728      foreach ( $all_headers as $field => $regex ) {
5729          if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_data, $match ) && $match[1] ) {
5730              $all_headers[ $field ] = _cleanup_header_comment( $match[1] );
5731          } else {
5732              $all_headers[ $field ] = '';
5733          }
5734      }
5735  
5736      return $all_headers;
5737  }
5738  
5739  /**
5740   * Returns true.
5741   *
5742   * Useful for returning true to filters easily.
5743   *
5744   * @since 3.0.0
5745   *
5746   * @see __return_false()
5747   *
5748   * @return true True.
5749   */
5750  function __return_true() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
5751      return true;
5752  }
5753  
5754  /**
5755   * Returns false.
5756   *
5757   * Useful for returning false to filters easily.
5758   *
5759   * @since 3.0.0
5760   *
5761   * @see __return_true()
5762   *
5763   * @return false False.
5764   */