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