[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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