[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  <?php
   2  /**
   3   * Main WordPress API
   4   *
   5   * @package WordPress
   6   */
   7  
   8  // Don't load directly.
   9  if ( ! defined( 'ABSPATH' ) ) {
  10      exit;
  11  }
  12  
  13  require  ABSPATH . WPINC . '/option.php';
  14  
  15  /**
  16   * Converts given MySQL date string into a different format.
  17   *
  18   *  - `$format` should be a PHP date format string.
  19   *  - 'U' and 'G' formats will return an integer sum of timestamp with timezone offset.
  20   *  - `$date` is expected to be local time in MySQL format (`Y-m-d H:i:s`).
  21   *
  22   * Historically UTC time could be passed to the function to produce Unix timestamp.
  23   *
  24   * If `$translate` is true then the given date and format string will
  25   * be passed to `wp_date()` for translation.
  26   *
  27   * @since 0.71
  28   *
  29   * @param string $format    Format of the date to return.
  30   * @param string $date      Date string to convert.
  31   * @param bool   $translate Whether the return date should be translated. Default true.
  32   * @return string|int|false Integer if `$format` is 'U' or 'G', string otherwise.
  33   *                          False on failure.
  34   *
  35   * @phpstan-return ( $format is 'U'|'G' ? int|false : string|false )
  36   */
  37  function mysql2date( $format, $date, $translate = true ) {
  38      if ( empty( $date ) ) {
  39          return false;
  40      }
  41  
  42      $timezone = wp_timezone();
  43      $datetime = date_create( $date, $timezone );
  44  
  45      if ( false === $datetime ) {
  46          return false;
  47      }
  48  
  49      // Returns a sum of timestamp with timezone offset. Ideally should never be used.
  50      if ( 'G' === $format || 'U' === $format ) {
  51          return $datetime->getTimestamp() + $datetime->getOffset();
  52      }
  53  
  54      if ( $translate ) {
  55          return wp_date( $format, $datetime->getTimestamp(), $timezone );
  56      }
  57  
  58      return $datetime->format( $format );
  59  }
  60  
  61  /**
  62   * Retrieves the current time based on specified type.
  63   *
  64   *  - The 'mysql' type will return the time in the format for MySQL DATETIME field.
  65   *  - The 'timestamp' or 'U' types will return the current timestamp or a sum of timestamp
  66   *    and timezone offset, depending on `$gmt`.
  67   *  - Other strings will be interpreted as PHP date formats (e.g. 'Y-m-d').
  68   *
  69   * If `$gmt` is a truthy value then both types will use GMT time, otherwise the
  70   * output is adjusted with the GMT offset for the site.
  71   *
  72   * @since 1.0.0
  73   * @since 5.3.0 Now returns an integer if `$type` is 'U'. Previously a string was returned.
  74   *
  75   * @param string $type Type of time to retrieve. Accepts 'mysql', 'timestamp', 'U',
  76   *                     or PHP date format string (e.g. 'Y-m-d').
  77   * @param bool   $gmt  Optional. Whether to use GMT timezone. Default false.
  78   * @return int|string Integer if `$type` is 'timestamp' or 'U', string otherwise.
  79   *
  80   * @phpstan-return ( $type is 'timestamp'|'U' ? int : string )
  81   */
  82  function current_time( $type, $gmt = false ) {
  83      // Don't use non-GMT timestamp, unless you know the difference and really need to.
  84      if ( 'timestamp' === $type || 'U' === $type ) {
  85          return $gmt ? time() : time() + (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
  86      }
  87  
  88      if ( 'mysql' === $type ) {
  89          $type = 'Y-m-d H:i:s';
  90      }
  91  
  92      $timezone = $gmt ? new DateTimeZone( 'UTC' ) : wp_timezone();
  93      $datetime = new DateTime( 'now', $timezone );
  94  
  95      return $datetime->format( $type );
  96  }
  97  
  98  /**
  99   * Retrieves the current time as an object using the site's timezone.
 100   *
 101   * @since 5.3.0
 102   *
 103   * @return DateTimeImmutable Date and time object.
 104   */
 105  function current_datetime() {
 106      return new DateTimeImmutable( 'now', wp_timezone() );
 107  }
 108  
 109  /**
 110   * Retrieves the timezone of the site as a string.
 111   *
 112   * Uses the `timezone_string` option to get a proper timezone name if available,
 113   * otherwise falls back to a manual UTC ± offset.
 114   *
 115   * Example return values:
 116   *
 117   *  - 'Europe/Rome'
 118   *  - 'America/North_Dakota/New_Salem'
 119   *  - 'UTC'
 120   *  - '-06:30'
 121   *  - '+00:00'
 122   *  - '+08:45'
 123   *
 124   * @since 5.3.0
 125   *
 126   * @return string PHP timezone name or a ±HH:MM offset.
 127   */
 128  function wp_timezone_string() {
 129      $timezone_string = get_option( 'timezone_string' );
 130  
 131      if ( $timezone_string ) {
 132          return $timezone_string;
 133      }
 134  
 135      $offset  = (float) get_option( 'gmt_offset' );
 136      $hours   = (int) $offset;
 137      $minutes = ( $offset - $hours );
 138  
 139      $sign      = ( $offset < 0 ) ? '-' : '+';
 140      $abs_hour  = abs( $hours );
 141      $abs_mins  = abs( $minutes * 60 );
 142      $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
 143  
 144      return $tz_offset;
 145  }
 146  
 147  /**
 148   * Retrieves the timezone of the site as a `DateTimeZone` object.
 149   *
 150   * Timezone can be based on a PHP timezone string or a ±HH:MM offset.
 151   *
 152   * @since 5.3.0
 153   *
 154   * @return DateTimeZone Timezone object.
 155   */
 156  function wp_timezone() {
 157      return new DateTimeZone( wp_timezone_string() );
 158  }
 159  
 160  /**
 161   * Retrieves the date in localized format, based on a sum of Unix timestamp and
 162   * timezone offset in seconds.
 163   *
 164   * If the locale specifies the locale month and weekday, then the locale will
 165   * take over the format for the date. If it isn't, then the date format string
 166   * will be used instead.
 167   *
 168   * Note that due to the way WP typically generates a sum of timestamp and offset
 169   * with `strtotime()`, it implies offset added at a _current_ time, not at the time
 170   * the timestamp represents. Storing such timestamps or calculating them differently
 171   * will lead to invalid output.
 172   *
 173   * @since 0.71
 174   * @since 5.3.0 Converted into a wrapper for wp_date().
 175   *
 176   * @param string   $format                Format to display the date.
 177   * @param int|bool $timestamp_with_offset Optional. A sum of Unix timestamp and timezone offset
 178   *                                        in seconds. Default false.
 179   * @param bool     $gmt                   Optional. Whether to use GMT timezone. Only applies
 180   *                                        if timestamp is not provided. Default false.
 181   * @return string The date, translated if locale specifies it.
 182   */
 183  function date_i18n( $format, $timestamp_with_offset = false, $gmt = false ) {
 184      $timestamp = $timestamp_with_offset;
 185  
 186      // If timestamp is omitted it should be current time (summed with offset, unless `$gmt` is true).
 187      if ( ! is_numeric( $timestamp ) ) {
 188          // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
 189          $timestamp = current_time( 'timestamp', $gmt );
 190      }
 191  
 192      /*
 193       * This is a legacy implementation quirk that the returned timestamp is also with offset.
 194       * Ideally this function should never be used to produce a timestamp.
 195       */
 196      if ( 'U' === $format ) {
 197          $date = $timestamp;
 198      } elseif ( $gmt && false === $timestamp_with_offset ) { // Current time in UTC.
 199          $date = wp_date( $format, null, new DateTimeZone( 'UTC' ) );
 200      } elseif ( false === $timestamp_with_offset ) { // Current time in site's timezone.
 201          $date = wp_date( $format );
 202      } else {
 203          /*
 204           * Timestamp with offset is typically produced by a UTC `strtotime()` call on an input without timezone.
 205           * This is the best attempt to reverse that operation into a local time to use.
 206           */
 207          $local_time = gmdate( 'Y-m-d H:i:s', $timestamp );
 208          $timezone   = wp_timezone();
 209          $datetime   = date_create( $local_time, $timezone );
 210          $date       = wp_date( $format, $datetime->getTimestamp(), $timezone );
 211      }
 212  
 213      /**
 214       * Filters the date formatted based on the locale.
 215       *
 216       * @since 2.8.0
 217       *
 218       * @param string $date      Formatted date string.
 219       * @param string $format    Format to display the date.
 220       * @param int    $timestamp A sum of Unix timestamp and timezone offset in seconds.
 221       *                          Might be without offset if input omitted timestamp but requested GMT.
 222       * @param bool   $gmt       Whether to use GMT timezone. Only applies if timestamp was not provided.
 223       */
 224      $date = apply_filters( 'date_i18n', $date, $format, $timestamp, $gmt );
 225  
 226      return $date;
 227  }
 228  
 229  /**
 230   * Retrieves the date, in localized format.
 231   *
 232   * This is a newer function, intended to replace `date_i18n()` without legacy quirks in it.
 233   *
 234   * Note that, unlike `date_i18n()`, this function accepts a true Unix timestamp, not summed
 235   * with timezone offset.
 236   *
 237   * @since 5.3.0
 238   *
 239   * @global WP_Locale $wp_locale WordPress date and time locale object.
 240   *
 241   * @param string            $format    PHP date format.
 242   * @param int|null          $timestamp Optional. Unix timestamp. Defaults to current time.
 243   * @param DateTimeZone|null $timezone  Optional. Timezone to output result in. Defaults to timezone
 244   *                                     from site settings.
 245   * @return string|false The date, translated if locale specifies it. False on invalid timestamp input.
 246   */
 247  function wp_date( $format, $timestamp = null, $timezone = null ) {
 248      global $wp_locale;
 249  
 250      if ( null === $timestamp ) {
 251          $timestamp = time();
 252      } elseif ( ! is_numeric( $timestamp ) ) {
 253          return false;
 254      }
 255  
 256      if ( ! $timezone ) {
 257          $timezone = wp_timezone();
 258      }
 259  
 260      $datetime = date_create( '@' . $timestamp );
 261      $datetime->setTimezone( $timezone );
 262  
 263      if ( empty( $wp_locale->month ) || empty( $wp_locale->weekday ) ) {
 264          $date = $datetime->format( $format );
 265      } else {
 266          // We need to unpack shorthand `r` format because it has parts that might be localized.
 267          $format = preg_replace( '/(?<!\\\\)r/', DATE_RFC2822, $format );
 268  
 269          $new_format    = '';
 270          $format_length = strlen( $format );
 271          $month         = $wp_locale->get_month( $datetime->format( 'm' ) );
 272          $weekday       = $wp_locale->get_weekday( $datetime->format( 'w' ) );
 273  
 274          for ( $i = 0; $i < $format_length; $i++ ) {
 275              switch ( $format[ $i ] ) {
 276                  case 'D':
 277                      $new_format .= addcslashes( $wp_locale->get_weekday_abbrev( $weekday ), '\\A..Za..z' );
 278                      break;
 279                  case 'F':
 280                      $new_format .= addcslashes( $month, '\\A..Za..z' );
 281                      break;
 282                  case 'l':
 283                      $new_format .= addcslashes( $weekday, '\\A..Za..z' );
 284                      break;
 285                  case 'M':
 286                      $new_format .= addcslashes( $wp_locale->get_month_abbrev( $month ), '\\A..Za..z' );
 287                      break;
 288                  case 'a':
 289                      $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'a' ) ), '\\A..Za..z' );
 290                      break;
 291                  case 'A':
 292                      $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'A' ) ), '\\A..Za..z' );
 293                      break;
 294                  case '\\':
 295                      $new_format .= $format[ $i ];
 296  
 297                      // If character follows a slash, we add it without translating.
 298                      if ( $i < $format_length ) {
 299                          $new_format .= $format[ ++$i ];
 300                      }
 301                      break;
 302                  default:
 303                      $new_format .= $format[ $i ];
 304                      break;
 305              }
 306          }
 307  
 308          $date = $datetime->format( $new_format );
 309          $date = wp_maybe_decline_date( $date, $format );
 310      }
 311  
 312      /**
 313       * Filters the date formatted based on the locale.
 314       *
 315       * @since 5.3.0
 316       *
 317       * @param string       $date      Formatted date string.
 318       * @param string       $format    Format to display the date.
 319       * @param int          $timestamp Unix timestamp.
 320       * @param DateTimeZone $timezone  Timezone.
 321       */
 322      $date = apply_filters( 'wp_date', $date, $format, $timestamp, $timezone );
 323  
 324      return $date;
 325  }
 326  
 327  /**
 328   * Determines if the date should be declined.
 329   *
 330   * If the locale specifies that month names require a genitive case in certain
 331   * formats (like 'j F Y'), the month name will be replaced with a correct form.
 332   *
 333   * @since 4.4.0
 334   * @since 5.4.0 The `$format` parameter was added.
 335   *
 336   * @global WP_Locale $wp_locale WordPress date and time locale object.
 337   *
 338   * @param string $date   Formatted date string.
 339   * @param string $format Optional. Date format to check. Default empty string.
 340   * @return string The date, declined if locale specifies it.
 341   */
 342  function wp_maybe_decline_date( $date, $format = '' ) {
 343      global $wp_locale;
 344  
 345      // i18n functions are not available in SHORTINIT mode.
 346      if ( ! function_exists( '_x' ) ) {
 347          return $date;
 348      }
 349  
 350      /*
 351       * translators: If months in your language require a genitive case,
 352       * translate this to 'on'. Do not translate into your own language.
 353       */
 354      if ( 'on' === _x( 'off', 'decline months names: on or off' ) ) {
 355  
 356          $months          = $wp_locale->month;
 357          $months_genitive = $wp_locale->month_genitive;
 358  
 359          /*
 360           * Match a format like 'j F Y' or 'j. F' (day of the month, followed by month name)
 361           * and decline the month.
 362           */
 363          if ( $format ) {
 364              $decline = preg_match( '#[dj]\.? F#', $format );
 365          } else {
 366              // If the format is not passed, try to guess it from the date string.
 367              $decline = preg_match( '#\b\d{1,2}\.? [^\d ]+\b#u', $date );
 368          }
 369  
 370          if ( $decline ) {
 371              foreach ( $months as $key => $month ) {
 372                  $months[ $key ] = '# ' . preg_quote( $month, '#' ) . '\b#u';
 373              }
 374  
 375              foreach ( $months_genitive as $key => $month ) {
 376                  $months_genitive[ $key ] = ' ' . $month;
 377              }
 378  
 379              $date = preg_replace( $months, $months_genitive, $date );
 380          }
 381  
 382          /*
 383           * Match a format like 'F jS' or 'F j' (month name, followed by day with an optional ordinal suffix)
 384           * and change it to declined 'j F'.
 385           */
 386          if ( $format ) {
 387              $decline = preg_match( '#F [dj]#', $format );
 388          } else {
 389              // If the format is not passed, try to guess it from the date string.
 390              $decline = preg_match( '#\b[^\d ]+ \d{1,2}(st|nd|rd|th)?\b#u', trim( $date ) );
 391          }
 392  
 393          if ( $decline ) {
 394              foreach ( $months as $key => $month ) {
 395                  $months[ $key ] = '#\b' . preg_quote( $month, '#' ) . ' (\d{1,2})(st|nd|rd|th)?([-–]\d{1,2})?(st|nd|rd|th)?\b#u';
 396              }
 397  
 398              foreach ( $months_genitive as $key => $month ) {
 399                  $months_genitive[ $key ] = '$1$3 ' . $month;
 400              }
 401  
 402              $date = preg_replace( $months, $months_genitive, $date );
 403          }
 404      }
 405  
 406      // Used for locale-specific rules.
 407      $locale = get_locale();
 408  
 409      if ( 'ca' === $locale ) {
 410          // " de abril| de agost| de octubre..." -> " d'abril| d'agost| d'octubre..."
 411          $date = preg_replace( '# de ([ao])#i', " d'\\1", $date );
 412      }
 413  
 414      return $date;
 415  }
 416  
 417  /**
 418   * Converts float number to format based on the locale.
 419   *
 420   * @since 2.3.0
 421   *
 422   * @global WP_Locale $wp_locale WordPress date and time locale object.
 423   *
 424   * @param float $number   The number to convert based on locale.
 425   * @param int   $decimals Optional. Precision of the number of decimal places. Default 0.
 426   * @return string Converted number in string format.
 427   */
 428  function number_format_i18n( $number, $decimals = 0 ) {
 429      global $wp_locale;
 430  
 431      if ( isset( $wp_locale ) ) {
 432          $formatted = number_format( $number, absint( $decimals ), $wp_locale->number_format['decimal_point'], $wp_locale->number_format['thousands_sep'] );
 433      } else {
 434          $formatted = number_format( $number, absint( $decimals ) );
 435      }
 436  
 437      /**
 438       * Filters the number formatted based on the locale.
 439       *
 440       * @since 2.8.0
 441       * @since 4.9.0 The `$number` and `$decimals` parameters were added.
 442       *
 443       * @param string $formatted Converted number in string format.
 444       * @param float  $number    The number to convert based on locale.
 445       * @param int    $decimals  Precision of the number of decimal places.
 446       */
 447      return apply_filters( 'number_format_i18n', $formatted, $number, $decimals );
 448  }
 449  
 450  /**
 451   * Converts a number of bytes to the largest unit the bytes will fit into.
 452   *
 453   * It is easier to read 1 KB than 1024 bytes and 1 MB than 1048576 bytes. Converts
 454   * number of bytes to human readable number by taking the number of that unit
 455   * that the bytes will go into it. Supports YB value.
 456   *
 457   * Please note that integers in PHP are limited to 32 bits, unless they are on
 458   * 64 bit architecture, then they have 64 bit size. If you need to place the
 459   * larger size then what PHP integer type will hold, then use a string. It will
 460   * be converted to a double, which should always have 64 bit length.
 461   *
 462   * Technically the correct unit names for powers of 1024 are KiB, MiB etc.
 463   *
 464   * @since 2.3.0
 465   * @since 6.0.0 Support for PB, EB, ZB, and YB was added.
 466   *
 467   * @param int|float|string $bytes    Number of bytes. Note max integer size for integers.
 468   * @param int              $decimals Optional. Precision of number of decimal places. Default 0.
 469   * @return string|false Number string on success, false on failure.
 470   *
 471   * @phpstan-param int|float|numeric-string $bytes
 472   *
 473   * @phpstan-return ( $bytes is int<0, max> ? string : string|false )
 474   */
 475  function size_format( $bytes, $decimals = 0 ) {
 476      if ( ! is_numeric( $bytes ) ) {
 477          return false;
 478      }
 479  
 480      $bytes = (float) $bytes;
 481  
 482      $quant = array(
 483          /* translators: Unit symbol for yottabyte. */
 484          _x( 'YB', 'unit symbol' ) => YB_IN_BYTES,
 485          /* translators: Unit symbol for zettabyte. */
 486          _x( 'ZB', 'unit symbol' ) => ZB_IN_BYTES,
 487          /* translators: Unit symbol for exabyte. */
 488          _x( 'EB', 'unit symbol' ) => EB_IN_BYTES,
 489          /* translators: Unit symbol for petabyte. */
 490          _x( 'PB', 'unit symbol' ) => PB_IN_BYTES,
 491          /* translators: Unit symbol for terabyte. */
 492          _x( 'TB', 'unit symbol' ) => TB_IN_BYTES,
 493          /* translators: Unit symbol for gigabyte. */
 494          _x( 'GB', 'unit symbol' ) => GB_IN_BYTES,
 495          /* translators: Unit symbol for megabyte. */
 496          _x( 'MB', 'unit symbol' ) => MB_IN_BYTES,
 497          /* translators: Unit symbol for kilobyte. */
 498          _x( 'KB', 'unit symbol' ) => KB_IN_BYTES,
 499          /* translators: Unit symbol for byte. */
 500          _x( 'B', 'unit symbol' )  => 1,
 501      );
 502  
 503      if ( 0.0 === $bytes ) {
 504          /* translators: Unit symbol for byte. */
 505          return number_format_i18n( 0, $decimals ) . ' ' . _x( 'B', 'unit symbol' );
 506      }
 507  
 508      foreach ( $quant as $unit => $mag ) {
 509          if ( $bytes >= $mag ) {
 510              return number_format_i18n( $bytes / $mag, $decimals ) . ' ' . $unit;
 511          }
 512      }
 513  
 514      return false;
 515  }
 516  
 517  /**
 518   * Converts a duration to human readable format.
 519   *
 520   * @since 5.1.0
 521   *
 522   * @param string $duration Duration will be in string format (HH:ii:ss) OR (ii:ss),
 523   *                         with a possible prepended negative sign (-).
 524   * @return string|false A human readable duration string, false on failure.
 525   */
 526  function human_readable_duration( $duration = '' ) {
 527      if ( ( empty( $duration ) || ! is_string( $duration ) ) ) {
 528          return false;
 529      }
 530  
 531      $duration = trim( $duration );
 532  
 533      // Remove prepended negative sign.
 534      if ( str_starts_with( $duration, '-' ) ) {
 535          $duration = substr( $duration, 1 );
 536      }
 537  
 538      // Extract duration parts.
 539      $duration_parts = array_reverse( explode( ':', $duration ) );
 540      $duration_count = count( $duration_parts );
 541  
 542      $hour   = null;
 543      $minute = null;
 544      $second = null;
 545  
 546      if ( 3 === $duration_count ) {
 547          // Validate HH:ii:ss duration format.
 548          if ( ! ( (bool) preg_match( '/^([0-9]+):([0-5]?[0-9]):([0-5]?[0-9])$/', $duration ) ) ) {
 549              return false;
 550          }
 551          // Three parts: hours, minutes & seconds.
 552          list( $second, $minute, $hour ) = $duration_parts;
 553      } elseif ( 2 === $duration_count ) {
 554          // Validate ii:ss duration format.
 555          if ( ! ( (bool) preg_match( '/^([0-5]?[0-9]):([0-5]?[0-9])$/', $duration ) ) ) {
 556              return false;
 557          }
 558          // Two parts: minutes & seconds.
 559          list( $second, $minute ) = $duration_parts;
 560      } else {
 561          return false;
 562      }
 563  
 564      $human_readable_duration = array();
 565  
 566      // Add the hour part to the string.
 567      if ( is_numeric( $hour ) ) {
 568          /* translators: %s: Time duration in hour or hours. */
 569          $human_readable_duration[] = sprintf( _n( '%s hour', '%s hours', $hour ), (int) $hour );
 570      }
 571  
 572      // Add the minute part to the string.
 573      if ( is_numeric( $minute ) ) {
 574          /* translators: %s: Time duration in minute or minutes. */
 575          $human_readable_duration[] = sprintf( _n( '%s minute', '%s minutes', $minute ), (int) $minute );
 576      }
 577  
 578      // Add the second part to the string.
 579      if ( is_numeric( $second ) ) {
 580          /* translators: %s: Time duration in second or seconds. */
 581          $human_readable_duration[] = sprintf( _n( '%s second', '%s seconds', $second ), (int) $second );
 582      }
 583  
 584      return implode( ', ', $human_readable_duration );
 585  }
 586  
 587  /**
 588   * Gets the week start and end from the datetime or date string from MySQL.
 589   *
 590   * @since 0.71
 591   *
 592   * @param string     $mysqlstring   Date or datetime field type from MySQL.
 593   * @param int|string $start_of_week Optional. Start of the week as an integer. Default empty string.
 594   * @return int[] {
 595   *     Week start and end dates as Unix timestamps.
 596   *
 597   *     @type int $start The week start date as a Unix timestamp.
 598   *     @type int $end   The week end date as a Unix timestamp.
 599   * }
 600   */
 601  function get_weekstartend( $mysqlstring, $start_of_week = '' ) {
 602      // MySQL string year.
 603      $mysql_year = substr( $mysqlstring, 0, 4 );
 604  
 605      // MySQL string month.
 606      $mysql_month = substr( $mysqlstring, 5, 2 );
 607  
 608      // MySQL string day.
 609      $mysql_day = substr( $mysqlstring, 8, 2 );
 610  
 611      // The timestamp for MySQL string day.
 612      $day = mktime( 0, 0, 0, $mysql_month, $mysql_day, $mysql_year );
 613  
 614      // The day of the week from the timestamp.
 615      $weekday = (int) gmdate( 'w', $day );
 616  
 617      if ( ! is_numeric( $start_of_week ) ) {
 618          $start_of_week = (int) get_option( 'start_of_week' );
 619      }
 620  
 621      if ( $weekday < $start_of_week ) {
 622          $weekday += 7;
 623      }
 624  
 625      // The most recent week start day on or before $day.
 626      $start = $day - DAY_IN_SECONDS * ( $weekday - $start_of_week );
 627  
 628      // $start + 1 week - 1 second.
 629      $end = $start + WEEK_IN_SECONDS - 1;
 630  
 631      return compact( 'start', 'end' );
 632  }
 633  
 634  /**
 635   * Serializes data, if needed.
 636   *
 637   * @since 2.0.5
 638   *
 639   * @param string|array|object $data Data that might be serialized.
 640   * @return mixed A scalar data.
 641   *
 642   * @phpstan-template T of mixed
 643   * @phpstan-param T $data
 644   * @phpstan-return ( T is array|object|string ? string : T )
 645   */
 646  function maybe_serialize( $data ) {
 647      if ( is_array( $data ) || is_object( $data ) ) {
 648          return serialize( $data );
 649      }
 650  
 651      /*
 652       * Double serialization is required for backward compatibility.
 653       * See https://core.trac.wordpress.org/ticket/12930
 654       * Also the world will end. See WP 3.6.1.
 655       */
 656      if ( is_serialized( $data, false ) ) {
 657          return serialize( $data );
 658      }
 659  
 660      return $data;
 661  }
 662  
 663  /**
 664   * Unserializes data only if it was serialized.
 665   *
 666   * @since 2.0.0
 667   *
 668   * @param string $data Data that might be unserialized.
 669   * @return mixed Unserialized data can be any type.
 670   */
 671  function maybe_unserialize( $data ) {
 672      if ( is_serialized( $data ) ) { // Don't attempt to unserialize data that wasn't serialized going in.
 673          return @unserialize( trim( $data ) );
 674      }
 675  
 676      return $data;
 677  }
 678  
 679  /**
 680   * Checks value to find if it was serialized.
 681   *
 682   * If $data is not a string, then returned value will always be false.
 683   * Serialized data is always a string.
 684   *
 685   * @since 2.0.5
 686   * @since 6.1.0 Added Enum support.
 687   *
 688   * @param string $data   Value to check to see if was serialized.
 689   * @param bool   $strict Optional. Whether to be strict about the end of the string. Default true.
 690   * @return bool False if not serialized and true if it was.
 691   */
 692  function is_serialized( $data, $strict = true ) {
 693      // If it isn't a string, it isn't serialized.
 694      if ( ! is_string( $data ) ) {
 695          return false;
 696      }
 697      $data = trim( $data );
 698      if ( 'N;' === $data ) {
 699          return true;
 700      }
 701      if ( strlen( $data ) < 4 ) {
 702          return false;
 703      }
 704      if ( ':' !== $data[1] ) {
 705          return false;
 706      }
 707      if ( $strict ) {
 708          $lastc = substr( $data, -1 );
 709          if ( ';' !== $lastc && '}' !== $lastc ) {
 710              return false;
 711          }
 712      } else {
 713          $semicolon = strpos( $data, ';' );
 714          $brace     = strpos( $data, '}' );
 715          // Either ; or } must exist.
 716          if ( false === $semicolon && false === $brace ) {
 717              return false;
 718          }
 719          // But neither must be in the first X characters.
 720          if ( false !== $semicolon && $semicolon < 3 ) {
 721              return false;
 722          }
 723          if ( false !== $brace && $brace < 4 ) {
 724              return false;
 725          }
 726      }
 727      $token = $data[0];
 728      switch ( $token ) {
 729          case 's':
 730              if ( $strict ) {
 731                  if ( '"' !== substr( $data, -2, 1 ) ) {
 732                      return false;
 733                  }
 734              } elseif ( ! str_contains( $data, '"' ) ) {
 735                  return false;
 736              }
 737              // Or else fall through.
 738          case 'a':
 739          case 'O':
 740          case 'E':
 741              return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
 742          case 'b':
 743          case 'i':
 744          case 'd':
 745              $end = $strict ? '$' : '';
 746              return (bool) preg_match( "/^{$token}:[0-9.E+-]+;$end/", $data );
 747      }
 748      return false;
 749  }
 750  
 751  /**
 752   * Checks whether serialized data is of string type.
 753   *
 754   * @since 2.0.5
 755   *
 756   * @param string $data Serialized data.
 757   * @return bool False if not a serialized string, true if it is.
 758   */
 759  function is_serialized_string( $data ) {
 760      // if it isn't a string, it isn't a serialized string.
 761      if ( ! is_string( $data ) ) {
 762          return false;
 763      }
 764      $data = trim( $data );
 765      if ( strlen( $data ) < 4 ) {
 766          return false;
 767      } elseif ( ':' !== $data[1] ) {
 768          return false;
 769      } elseif ( ! str_ends_with( $data, ';' ) ) {
 770          return false;
 771      } elseif ( 's' !== $data[0] ) {
 772          return false;
 773      } elseif ( '"' !== substr( $data, -2, 1 ) ) {
 774          return false;
 775      } else {
 776          return true;
 777      }
 778  }
 779  
 780  /**
 781   * Retrieves post title from XML-RPC XML.
 782   *
 783   * If the `title` element is not found in the XML, the default post title
 784   * from the `$post_default_title` global will be used instead.
 785   *
 786   * @since 0.71
 787   *
 788   * @global string $post_default_title Default XML-RPC post title.
 789   *
 790   * @param string $content XML-RPC XML Request content.
 791   * @return string Post title.
 792   */
 793  function xmlrpc_getposttitle( $content ) {
 794      global $post_default_title;
 795      if ( preg_match( '/<title>(.+?)<\/title>/is', $content, $matchtitle ) ) {
 796          $post_title = $matchtitle[1];
 797      } else {
 798          $post_title = $post_default_title;
 799      }
 800      return $post_title;
 801  }
 802  
 803  /**
 804   * Retrieves the post category or categories from XML-RPC XML.
 805   *
 806   * If the `category` element is not found in the XML, the default post category
 807   * from the `$post_default_category` global will be used instead.
 808   * The return type will then be a string.
 809   *
 810   * If the `category` element is found, the return type will be an array.
 811   *
 812   * @since 0.71
 813   *
 814   * @global string $post_default_category Default XML-RPC post category.
 815   *
 816   * @param string $content XML-RPC XML Request content.
 817   * @return string[]|string An array of category names or default category name.
 818   */
 819  function xmlrpc_getpostcategory( $content ) {
 820      global $post_default_category;
 821      if ( preg_match( '/<category>(.+?)<\/category>/is', $content, $matchcat ) ) {
 822          $post_category = trim( $matchcat[1], ',' );
 823          $post_category = explode( ',', $post_category );
 824      } else {
 825          $post_category = $post_default_category;
 826      }
 827      return $post_category;
 828  }
 829  
 830  /**
 831   * XML-RPC XML content without title and category elements.
 832   *
 833   * @since 0.71
 834   *
 835   * @param string $content XML-RPC XML Request content.
 836   * @return string XML-RPC XML Request content without title and category elements.
 837   */
 838  function xmlrpc_removepostdata( $content ) {
 839      $content = preg_replace( '/<title>(.+?)<\/title>/si', '', $content );
 840      $content = preg_replace( '/<category>(.+?)<\/category>/si', '', $content );
 841      $content = trim( $content );
 842      return $content;
 843  }
 844  
 845  /**
 846   * Uses RegEx to extract URLs from arbitrary content.
 847   *
 848   * @since 3.7.0
 849   * @since 6.0.0 Fixes support for HTML entities (Trac 30580).
 850   *
 851   * @param string $content Content to extract URLs from.
 852   * @return string[] Array of URLs found in passed string.
 853   *
 854   * @phpstan-return ( $content is empty ? array{} : list<string> )
 855   */
 856  function wp_extract_urls( $content ) {
 857      preg_match_all(
 858          "#([\"']?)("
 859              . '(?:([\w-]+:)?//?)'
 860              . '[^\s()<>]+'
 861              . '[.]'
 862              . '(?:'
 863                  . '\([\w\d]+\)|'
 864                  . '(?:'
 865                      . "[^`!()\[\]{}:'\".,<>«»“”‘’\s]|"
 866                      . '(?:[:]\d+)?/?'
 867                  . ')+'
 868              . ')'
 869          . ")\\1#",
 870          $content,
 871          $post_links
 872      );
 873  
 874      $post_links = array_unique(
 875          array_map(
 876              static function ( $link ) {
 877                  // Decode to replace valid entities, like &amp;.
 878                  $link = html_entity_decode( $link );
 879                  // Maintain backward compatibility by removing extraneous semi-colons (`;`).
 880                  return str_replace( ';', '', $link );
 881              },
 882              $post_links[2]
 883          )
 884      );
 885  
 886      return array_values( $post_links );
 887  }
 888  
 889  /**
 890   * Checks content for video and audio links to add as enclosures.
 891   *
 892   * Will not add enclosures that have already been added and will
 893   * remove enclosures that are no longer in the post. This is called as
 894   * pingbacks and trackbacks.
 895   *
 896   * @since 1.5.0
 897   * @since 5.3.0 The `$content` parameter was made optional, and the `$post` parameter was
 898   *              updated to accept a post ID or a WP_Post object.
 899   * @since 5.6.0 The `$content` parameter is no longer optional, but passing `null` to skip it
 900   *              is still supported.
 901   *
 902   * @global wpdb $wpdb WordPress database abstraction object.
 903   *
 904   * @param string|null $content Post content. If `null`, the `post_content` field from `$post` is used.
 905   * @param int|WP_Post $post    Post ID or post object.
 906   * @return void|false Void on success, false if the post is not found.
 907   */
 908  function do_enclose( $content, $post ) {
 909      global $wpdb;
 910  
 911      // @todo Tidy this code and make the debug code optional.
 912      require_once  ABSPATH . WPINC . '/class-IXR.php';
 913  
 914      $post = get_post( $post );
 915      if ( ! $post ) {
 916          return false;
 917      }
 918  
 919      if ( null === $content ) {
 920          $content = $post->post_content;
 921      }
 922  
 923      $post_links = array();
 924  
 925      $pung = get_enclosed( $post->ID );
 926  
 927      $post_links_temp = wp_extract_urls( $content );
 928  
 929      foreach ( $pung as $link_test ) {
 930          // Link is no longer in post.
 931          if ( ! in_array( $link_test, $post_links_temp, true ) ) {
 932              $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 ) . '%' ) );
 933              foreach ( $mids as $mid ) {
 934                  delete_metadata_by_mid( 'post', $mid );
 935              }
 936          }
 937      }
 938  
 939      foreach ( (array) $post_links_temp as $link_test ) {
 940          // If we haven't pung it already.
 941          if ( ! in_array( $link_test, $pung, true ) ) {
 942              $test = parse_url( $link_test );
 943              if ( false === $test ) {
 944                  continue;
 945              }
 946              if ( isset( $test['query'] ) ) {
 947                  $post_links[] = $link_test;
 948              } elseif ( isset( $test['path'] ) && ( '/' !== $test['path'] ) && ( '' !== $test['path'] ) ) {
 949                  $post_links[] = $link_test;
 950              }
 951          }
 952      }
 953  
 954      /**
 955       * Filters the list of enclosure links before querying the database.
 956       *
 957       * Allows for the addition and/or removal of potential enclosures to save
 958       * to postmeta before checking the database for existing enclosures.
 959       *
 960       * @since 4.4.0
 961       *
 962       * @param string[] $post_links An array of enclosure links.
 963       * @param int      $post_id    Post ID.
 964       */
 965      $post_links = apply_filters( 'enclosure_links', $post_links, $post->ID );
 966  
 967      foreach ( (array) $post_links as $url ) {
 968          $url = strip_fragment_from_url( $url );
 969  
 970          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 ) . '%' ) ) ) {
 971  
 972              $headers = wp_get_http_headers( $url );
 973              if ( $headers ) {
 974                  $len           = (int) ( $headers['Content-Length'] ?? 0 );
 975                  $type          = $headers['Content-Type'] ?? '';
 976                  $allowed_types = array( 'video', 'audio' );
 977  
 978                  // Check to see if we can figure out the mime type from the extension.
 979                  $url_parts = parse_url( $url );
 980                  if ( false !== $url_parts && ! empty( $url_parts['path'] ) ) {
 981                      $extension = pathinfo( $url_parts['path'], PATHINFO_EXTENSION );
 982                      if ( ! empty( $extension ) ) {
 983                          foreach ( wp_get_mime_types() as $exts => $mime ) {
 984                              if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
 985                                  $type = $mime;
 986                                  break;
 987                              }
 988                          }
 989                      }
 990                  }
 991  
 992                  if ( in_array( substr( $type, 0, strpos( $type, '/' ) ), $allowed_types, true ) ) {
 993                      add_post_meta( $post->ID, 'enclosure', "$url\n$len\n$mime\n" );
 994                  }
 995              }
 996          }
 997      }
 998  }
 999  
1000  /**
1001   * Retrieves HTTP Headers from URL.
1002   *
1003   * @since 1.5.1
1004   *
1005   * @param string $url        URL to retrieve HTTP headers from.
1006   * @param bool   $deprecated Not Used.
1007   * @return \WpOrg\Requests\Utility\CaseInsensitiveDictionary|false Headers on success, false on failure.
1008   */
1009  function wp_get_http_headers( $url, $deprecated = false ) {
1010      if ( ! empty( $deprecated ) ) {
1011          _deprecated_argument( __FUNCTION__, '2.7.0' );
1012      }
1013  
1014      $response = wp_safe_remote_head( $url );
1015  
1016      if ( is_wp_error( $response ) ) {
1017          return false;
1018      }
1019  
1020      return wp_remote_retrieve_headers( $response );
1021  }
1022  
1023  /**
1024   * Determines whether the publish date of the current post in the loop is different
1025   * from the publish date of the previous post in the loop.
1026   *
1027   * For more information on this and similar theme functions, check out
1028   * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1029   * Conditional Tags} article in the Theme Developer Handbook.
1030   *
1031   * @since 0.71
1032   *
1033   * @global string $currentday  The day of the current post in the loop.
1034   * @global string $previousday The day of the previous post in the loop.
1035   *
1036   * @return int 1 when new day, 0 if not a new day.
1037   */
1038  function is_new_day() {
1039      global $currentday, $previousday;
1040  
1041      if ( $currentday !== $previousday ) {
1042          return 1;
1043      } else {
1044          return 0;
1045      }
1046  }
1047  
1048  /**
1049   * Builds a URL query based on an associative or indexed array.
1050   *
1051   * This is a convenient function for easily building URL queries.
1052   * It sets the separator to '&' and uses the _http_build_query() function.
1053   *
1054   * Unlike PHP's native http_build_query(), this function does NOT URL-encode
1055   * the keys or values. Callers are responsible for encoding values beforehand
1056   * with urlencode() or rawurlencode(), or late-escaping the output with
1057   * esc_url() before use.
1058   *
1059   * @since 2.3.0
1060   *
1061   * @see _http_build_query() Used to build the query.
1062   * @link https://www.php.net/manual/en/function.http-build-query.php for more on what
1063   *       http_build_query() does.
1064   *
1065   * @param array $data Array of key/value pairs to build the query from.
1066   * @return string Query string, without URL encoding applied.
1067   */
1068  function build_query( $data ) {
1069      return _http_build_query( $data, null, '&', '', false );
1070  }
1071  
1072  /**
1073   * From php.net (modified by Mark Jaquith to behave like the native PHP5 function).
1074   *
1075   * @since 3.2.0
1076   * @access private
1077   *
1078   * @see https://www.php.net/manual/en/function.http-build-query.php
1079   *
1080   * @param array|object $data      An array or object of data. Converted to array.
1081   * @param string       $prefix    Optional. Numeric index. If set, start parameter numbering with it.
1082   *                                Default null.
1083   * @param string       $sep       Optional. Argument separator; defaults to 'arg_separator.output'.
1084   *                                Default null.
1085   * @param string       $key       Optional. Used to prefix key name. Default empty string.
1086   * @param bool         $urlencode Optional. Whether to use urlencode() in the result. Default true.
1087   * @return string The query string.
1088   */
1089  function _http_build_query( $data, $prefix = null, $sep = null, $key = '', $urlencode = true ) {
1090      $ret = array();
1091  
1092      foreach ( (array) $data as $k => $v ) {
1093          if ( $urlencode ) {
1094              $k = urlencode( $k );
1095          }
1096  
1097          if ( is_int( $k ) && null !== $prefix ) {
1098              $k = $prefix . $k;
1099          }
1100  
1101          if ( ! empty( $key ) ) {
1102              $k = $key . '%5B' . $k . '%5D';
1103          }
1104  
1105          if ( null === $v ) {
1106              continue;
1107          } elseif ( false === $v ) {
1108              $v = '0';
1109          }
1110  
1111          if ( is_array( $v ) || is_object( $v ) ) {
1112              array_push( $ret, _http_build_query( $v, '', $sep, $k, $urlencode ) );
1113          } elseif ( $urlencode ) {
1114              array_push( $ret, $k . '=' . urlencode( $v ) );
1115          } else {
1116              array_push( $ret, $k . '=' . $v );
1117          }
1118      }
1119  
1120      if ( null === $sep ) {
1121          $sep = ini_get( 'arg_separator.output' );
1122      }
1123  
1124      return implode( $sep, $ret );
1125  }
1126  
1127  /**
1128   * Retrieves a modified URL query string.
1129   *
1130   * You can rebuild the URL and append query variables to the URL query by using this function.
1131   * There are two ways to use this function; either a single key and value, or an associative array.
1132   *
1133   * Using a single key and value:
1134   *
1135   *     add_query_arg( 'key', 'value', 'http://example.com' );
1136   *
1137   * Using an associative array:
1138   *
1139   *     add_query_arg( array(
1140   *         'key1' => 'value1',
1141   *         'key2' => 'value2',
1142   *     ), 'http://example.com' );
1143   *
1144   * Omitting the URL from either use results in the current URL being used
1145   * (the value of `$_SERVER['REQUEST_URI']`).
1146   *
1147   * Values are expected to be encoded appropriately with urlencode() or rawurlencode().
1148   *
1149   * Setting any query variable's value to boolean false removes the key (see remove_query_arg()).
1150   *
1151   * Important: The return value of add_query_arg() is not escaped by default. Output should be
1152   * late-escaped with esc_url() or similar to help prevent vulnerability to cross-site scripting
1153   * (XSS) attacks.
1154   *
1155   * @since 1.5.0
1156   * @since 5.3.0 Formalized the existing and already documented parameters
1157   *              by adding `...$args` to the function signature.
1158   *
1159   * @param string|array $key   Either a query variable key, or an associative array of query variables.
1160   * @param string       $value Optional. Either a query variable value, or a URL to act upon.
1161   * @param string       $url   Optional. A URL to act upon.
1162   * @return string New URL query string (unescaped).
1163   */
1164  function add_query_arg( ...$args ) {
1165      if ( is_array( $args[0] ) ) {
1166          if ( count( $args ) < 2 || false === $args[1] ) {
1167              $uri = $_SERVER['REQUEST_URI'];
1168          } else {
1169              $uri = $args[1];
1170          }
1171      } else {
1172          if ( count( $args ) < 3 || false === $args[2] ) {
1173              $uri = $_SERVER['REQUEST_URI'];
1174          } else {
1175              $uri = $args[2];
1176          }
1177      }
1178  
1179      $frag = strstr( $uri, '#' );
1180      if ( $frag ) {
1181          $uri = substr( $uri, 0, -strlen( $frag ) );
1182      } else {
1183          $frag = '';
1184      }
1185  
1186      if ( 0 === stripos( $uri, 'http://' ) ) {
1187          $protocol = 'http://';
1188          $uri      = substr( $uri, 7 );
1189      } elseif ( 0 === stripos( $uri, 'https://' ) ) {
1190          $protocol = 'https://';
1191          $uri      = substr( $uri, 8 );
1192      } else {
1193          $protocol = '';
1194      }
1195  
1196      if ( str_contains( $uri, '?' ) ) {
1197          list( $base, $query ) = explode( '?', $uri, 2 );
1198          $base                .= '?';
1199      } elseif ( $protocol || ! str_contains( $uri, '=' ) ) {
1200          $base  = $uri . '?';
1201          $query = '';
1202      } else {
1203          $base  = '';
1204          $query = $uri;
1205      }
1206  
1207      wp_parse_str( $query, $qs );
1208      $qs = urlencode_deep( $qs ); // This re-URL-encodes things that were already in the query string.
1209      if ( is_array( $args[0] ) ) {
1210          foreach ( $args[0] as $k => $v ) {
1211              $qs[ $k ] = $v;
1212          }
1213      } else {
1214          $qs[ $args[0] ] = $args[1];
1215      }
1216  
1217      foreach ( $qs as $k => $v ) {
1218          if ( false === $v ) {
1219              unset( $qs[ $k ] );
1220          }
1221      }
1222  
1223      $ret = build_query( $qs );
1224      $ret = trim( $ret, '?' );
1225      $ret = preg_replace( '#=(&|$)#', '$1', $ret );
1226      $ret = $protocol . $base . $ret . $frag;
1227      $ret = rtrim( $ret, '?' );
1228      $ret = str_replace( '?#', '#', $ret );
1229      return $ret;
1230  }
1231  
1232  /**
1233   * Removes an item or items from a query string.
1234   *
1235   * Important: The return value of remove_query_arg() is not escaped by default. Output should be
1236   * late-escaped with esc_url() or similar to help prevent vulnerability to cross-site scripting
1237   * (XSS) attacks.
1238   *
1239   * @since 1.5.0
1240   *
1241   * @param string|string[] $key   Query key or keys to remove.
1242   * @param false|string    $query Optional. When false uses the current URL. Default false.
1243   * @return string New URL query string.
1244   */
1245  function remove_query_arg( $key, $query = false ) {
1246      if ( is_array( $key ) ) { // Removing multiple keys.
1247          foreach ( $key as $k ) {
1248              $query = add_query_arg( $k, false, $query );
1249          }
1250          return $query;
1251      }
1252      return add_query_arg( $key, false, $query );
1253  }
1254  
1255  /**
1256   * Returns an array of single-use query variable names that can be removed from a URL.
1257   *
1258   * @since 4.4.0
1259   *
1260   * @return string[] An array of query variable names to remove from the URL.
1261   */
1262  function wp_removable_query_args() {
1263      $removable_query_args = array(
1264          'activate',
1265          'activated',
1266          'admin_email_remind_later',
1267          'approved',
1268          'core-major-auto-updates-saved',
1269          'deactivate',
1270          'delete_count',
1271          'deleted',
1272          'disabled',
1273          'doing_wp_cron',
1274          'enabled',
1275          'error',
1276          'hotkeys_highlight_first',
1277          'hotkeys_highlight_last',
1278          'ids',
1279          'locked',
1280          'message',
1281          'same',
1282          'saved',
1283          'settings-updated',
1284          'skipped',
1285          'spammed',
1286          'trashed',
1287          'unspammed',
1288          'untrashed',
1289          'update',
1290          'updated',
1291          'wp-post-new-reload',
1292      );
1293  
1294      /**
1295       * Filters the list of query variable names to remove.
1296       *
1297       * @since 4.2.0
1298       *
1299       * @param string[] $removable_query_args An array of query variable names to remove from a URL.
1300       */
1301      return apply_filters( 'removable_query_args', $removable_query_args );
1302  }
1303  
1304  /**
1305   * Walks the array while sanitizing the contents.
1306   *
1307   * @since 0.71
1308   * @since 5.5.0 Non-string values are left untouched.
1309   *
1310   * @param array $input_array Array to walk while sanitizing contents.
1311   * @return array Sanitized $input_array.
1312   *
1313   * @phpstan-template T of array
1314   * @phpstan-param T $input_array
1315   * @phpstan-return array<key-of<T>, ( value-of<T> is string ? string : value-of<T> )>
1316   */
1317  function add_magic_quotes( $input_array ) {
1318      foreach ( (array) $input_array as $k => $v ) {
1319          if ( is_array( $v ) ) {
1320              $input_array[ $k ] = add_magic_quotes( $v );
1321          } elseif ( is_string( $v ) ) {
1322              $input_array[ $k ] = addslashes( $v );
1323          }
1324      }
1325  
1326      return $input_array;
1327  }
1328  
1329  /**
1330   * HTTP request for URI to retrieve content.
1331   *
1332   * @since 1.5.1
1333   *
1334   * @see wp_safe_remote_get()
1335   *
1336   * @param string $uri URI/URL of web page to retrieve.
1337   * @return string|false HTTP content. False on failure.
1338   */
1339  function wp_remote_fopen( $uri ) {
1340      $parsed_url = parse_url( $uri );
1341  
1342      if ( ! $parsed_url || ! is_array( $parsed_url ) ) {
1343          return false;
1344      }
1345  
1346      $options            = array();
1347      $options['timeout'] = 10;
1348  
1349      $response = wp_safe_remote_get( $uri, $options );
1350  
1351      if ( is_wp_error( $response ) ) {
1352          return false;
1353      }
1354  
1355      return wp_remote_retrieve_body( $response );
1356  }
1357  
1358  /**
1359   * Sets up the WordPress query.
1360   *
1361   * @since 2.0.0
1362   *
1363   * @global WP       $wp           Current WordPress environment instance.
1364   * @global WP_Query $wp_query     WordPress Query object.
1365   * @global WP_Query $wp_the_query Copy of the WordPress Query object.
1366   *
1367   * @param string|array $query_vars Default WP_Query arguments.
1368   */
1369  function wp( $query_vars = '' ) {
1370      global $wp, $wp_query, $wp_the_query;
1371  
1372      $wp->main( $query_vars );
1373  
1374      if ( ! isset( $wp_the_query ) ) {
1375          $wp_the_query = $wp_query;
1376      }
1377  }
1378  
1379  /**
1380   * Retrieves the description for the HTTP status.
1381   *
1382   * @since 2.3.0
1383   * @since 3.9.0 Added status codes 418, 428, 429, 431, and 511.
1384   * @since 4.5.0 Added status codes 308, 421, and 451.
1385   * @since 5.1.0 Added status code 103.
1386   * @since 6.6.0 Added status code 425.
1387   *
1388   * @global array $wp_header_to_desc
1389   *
1390   * @param int $code HTTP status code.
1391   * @return string Status description if found, an empty string otherwise.
1392   */
1393  function get_status_header_desc( $code ) {
1394      global $wp_header_to_desc;
1395  
1396      $code = absint( $code );
1397  
1398      if ( ! isset( $wp_header_to_desc ) ) {
1399          $wp_header_to_desc = array(
1400              100 => 'Continue',
1401              101 => 'Switching Protocols',
1402              102 => 'Processing',
1403              103 => 'Early Hints',
1404  
1405              200 => 'OK',
1406              201 => 'Created',
1407              202 => 'Accepted',
1408              203 => 'Non-Authoritative Information',
1409              204 => 'No Content',
1410              205 => 'Reset Content',
1411              206 => 'Partial Content',
1412              207 => 'Multi-Status',
1413              226 => 'IM Used',
1414  
1415              300 => 'Multiple Choices',
1416              301 => 'Moved Permanently',
1417              302 => 'Found',
1418              303 => 'See Other',
1419              304 => 'Not Modified',
1420              305 => 'Use Proxy',
1421              306 => 'Reserved',
1422              307 => 'Temporary Redirect',
1423              308 => 'Permanent Redirect',
1424  
1425              400 => 'Bad Request',
1426              401 => 'Unauthorized',
1427              402 => 'Payment Required',
1428              403 => 'Forbidden',
1429              404 => 'Not Found',
1430              405 => 'Method Not Allowed',
1431              406 => 'Not Acceptable',
1432              407 => 'Proxy Authentication Required',
1433              408 => 'Request Timeout',
1434              409 => 'Conflict',
1435              410 => 'Gone',
1436              411 => 'Length Required',
1437              412 => 'Precondition Failed',
1438              413 => 'Request Entity Too Large',
1439              414 => 'Request-URI Too Long',
1440              415 => 'Unsupported Media Type',
1441              416 => 'Requested Range Not Satisfiable',
1442              417 => 'Expectation Failed',
1443              418 => 'I\'m a teapot',
1444              421 => 'Misdirected Request',
1445              422 => 'Unprocessable Entity',
1446              423 => 'Locked',
1447              424 => 'Failed Dependency',
1448              425 => 'Too Early',
1449              426 => 'Upgrade Required',
1450              428 => 'Precondition Required',
1451              429 => 'Too Many Requests',
1452              431 => 'Request Header Fields Too Large',
1453              451 => 'Unavailable For Legal Reasons',
1454  
1455              500 => 'Internal Server Error',
1456              501 => 'Not Implemented',
1457              502 => 'Bad Gateway',
1458              503 => 'Service Unavailable',
1459              504 => 'Gateway Timeout',
1460              505 => 'HTTP Version Not Supported',
1461              506 => 'Variant Also Negotiates',
1462              507 => 'Insufficient Storage',
1463              510 => 'Not Extended',
1464              511 => 'Network Authentication Required',
1465          );
1466      }
1467  
1468      return $wp_header_to_desc[ $code ] ?? '';
1469  }
1470  
1471  /**
1472   * Sets HTTP status header.
1473   *
1474   * @since 2.0.0
1475   * @since 4.4.0 Added the `$description` parameter.
1476   *
1477   * @see get_status_header_desc()
1478   *
1479   * @param int    $code        HTTP status code.
1480   * @param string $description Optional. A custom description for the HTTP status.
1481   *                            Defaults to the result of get_status_header_desc() for the given code.
1482   */
1483  function status_header( $code, $description = '' ) {
1484      if ( ! $description ) {
1485          $description = get_status_header_desc( $code );
1486      }
1487  
1488      if ( empty( $description ) ) {
1489          return;
1490      }
1491  
1492      $protocol      = wp_get_server_protocol();
1493      $status_header = "$protocol $code $description";
1494      if ( function_exists( 'apply_filters' ) ) {
1495  
1496          /**
1497           * Filters an HTTP status header.
1498           *
1499           * @since 2.2.0
1500           *
1501           * @param string $status_header HTTP status header.
1502           * @param int    $code          HTTP status code.
1503           * @param string $description   Description for the status code.
1504           * @param string $protocol      Server protocol.
1505           */
1506          $status_header = apply_filters( 'status_header', $status_header, $code, $description, $protocol );
1507      }
1508  
1509      if ( ! headers_sent() ) {
1510          header( $status_header, true, $code );
1511      }
1512  }
1513  
1514  /**
1515   * Gets the HTTP header information to prevent caching.
1516   *
1517   * The several different headers cover the different ways cache prevention
1518   * is handled by different browsers or intermediate caches such as proxy servers.
1519   *
1520   * @since 2.8.0
1521   * @since 6.3.0 The `Cache-Control` header for logged in users now includes the
1522   *              `no-store` and `private` directives.
1523   * @since 6.8.0 The `Cache-Control` header now includes the `no-store` and `private`
1524   *              directives regardless of whether a user is logged in.
1525   *
1526   * @return array The associative array of header names and field values.
1527   */
1528  function wp_get_nocache_headers() {
1529      $cache_control = 'no-cache, must-revalidate, max-age=0, no-store, private';
1530  
1531      $headers = array(
1532          'Expires'       => 'Wed, 11 Jan 1984 05:00:00 GMT',
1533          'Cache-Control' => $cache_control,
1534      );
1535  
1536      if ( function_exists( 'apply_filters' ) ) {
1537          /**
1538           * Filters the cache-controlling HTTP headers that are used to prevent caching.
1539           *
1540           * @since 2.8.0
1541           *
1542           * @see wp_get_nocache_headers()
1543           *
1544           * @param array $headers Header names and field values.
1545           */
1546          $headers = (array) apply_filters( 'nocache_headers', $headers );
1547      }
1548      $headers['Last-Modified'] = false;
1549      return $headers;
1550  }
1551  
1552  /**
1553   * Sets the HTTP headers to prevent caching for the different browsers.
1554   *
1555   * Different browsers support different nocache headers, so several
1556   * headers must be sent so that all of them get the point that no
1557   * caching should occur.
1558   *
1559   * @since 2.0.0
1560   *
1561   * @see wp_get_nocache_headers()
1562   */
1563  function nocache_headers() {
1564      if ( headers_sent() ) {
1565          return;
1566      }
1567  
1568      $headers = wp_get_nocache_headers();
1569  
1570      unset( $headers['Last-Modified'] );
1571  
1572      header_remove( 'Last-Modified' );
1573  
1574      foreach ( $headers as $name => $field_value ) {
1575          header( "{$name}: {$field_value}" );
1576      }
1577  }
1578  
1579  /**
1580   * Sets the HTTP headers for caching for 10 days with JavaScript content type.
1581   *
1582   * @since 2.1.0
1583   */
1584  function cache_javascript_headers() {
1585      $expires_offset = 10 * DAY_IN_SECONDS;
1586  
1587      header( 'Content-Type: text/javascript; charset=' . get_bloginfo( 'charset' ) );
1588      header( 'Vary: Accept-Encoding' ); // Handle proxies.
1589      header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expires_offset ) . ' GMT' );
1590  }
1591  
1592  /**
1593   * Retrieves the number of database queries during the WordPress execution.
1594   *
1595   * @since 2.0.0
1596   *
1597   * @global wpdb $wpdb WordPress database abstraction object.
1598   *
1599   * @return int Number of database queries.
1600   */
1601  function get_num_queries() {
1602      global $wpdb;
1603      return $wpdb->num_queries;
1604  }
1605  
1606  /**
1607   * Determines whether input is yes or no.
1608   *
1609   * Must be 'y' to be true.
1610   *
1611   * @since 1.0.0
1612   *
1613   * @param string $yn Character string containing either 'y' (yes) or 'n' (no).
1614   * @return bool True if 'y', false on anything else.
1615   *
1616   * @phpstan-return ( $yn is 'y'|'Y' ? true : false )
1617   */
1618  function bool_from_yn( $yn ) {
1619      return ( 'y' === strtolower( $yn ) );
1620  }
1621  
1622  /**
1623   * Loads the feed template from the use of an action hook.
1624   *
1625   * If the feed action does not have a hook, then the function will die with a
1626   * message telling the visitor that the feed is not valid.
1627   *
1628   * It is better to only have one hook for each feed.
1629   *
1630   * @since 2.1.0
1631   *
1632   * @global WP_Query $wp_query WordPress Query object.
1633   */
1634  function do_feed() {
1635      global $wp_query;
1636  
1637      $feed = get_query_var( 'feed' );
1638  
1639      // Remove the pad, if present.
1640      $feed = preg_replace( '/^_+/', '', $feed );
1641  
1642      if ( '' === $feed || 'feed' === $feed ) {
1643          $feed = get_default_feed();
1644      }
1645  
1646      if ( ! has_action( "do_feed_{$feed}" ) ) {
1647          wp_die( __( '<strong>Error:</strong> This is not a valid feed template.' ), '', array( 'response' => 404 ) );
1648      }
1649  
1650      /**
1651       * Fires once the given feed is loaded.
1652       *
1653       * The dynamic portion of the hook name, `$feed`, refers to the feed template name.
1654       *
1655       * Possible hook names include:
1656       *
1657       *  - `do_feed_atom`
1658       *  - `do_feed_rdf`
1659       *  - `do_feed_rss`
1660       *  - `do_feed_rss2`
1661       *
1662       * @since 2.1.0
1663       * @since 4.4.0 The `$feed` parameter was added.
1664       *
1665       * @param bool   $is_comment_feed Whether the feed is a comment feed.
1666       * @param string $feed            The feed name.
1667       */
1668      do_action( "do_feed_{$feed}", $wp_query->is_comment_feed, $feed );
1669  }
1670  
1671  /**
1672   * Loads the RDF RSS 0.91 Feed template.
1673   *
1674   * @since 2.1.0
1675   *
1676   * @see load_template()
1677   */
1678  function do_feed_rdf() {
1679      load_template( ABSPATH . WPINC . '/feed-rdf.php' );
1680  }
1681  
1682  /**
1683   * Loads the RSS 1.0 Feed Template.
1684   *
1685   * @since 2.1.0
1686   *
1687   * @see load_template()
1688   */
1689  function do_feed_rss() {
1690      load_template( ABSPATH . WPINC . '/feed-rss.php' );
1691  }
1692  
1693  /**
1694   * Loads either the RSS2 comment feed or the RSS2 posts feed.
1695   *
1696   * @since 2.1.0
1697   *
1698   * @see load_template()
1699   *
1700   * @param bool $for_comments True for the comment feed, false for normal feed.
1701   */
1702  function do_feed_rss2( $for_comments ) {
1703      if ( $for_comments ) {
1704          load_template( ABSPATH . WPINC . '/feed-rss2-comments.php' );
1705      } else {
1706          load_template( ABSPATH . WPINC . '/feed-rss2.php' );
1707      }
1708  }
1709  
1710  /**
1711   * Loads either Atom comment feed or Atom posts feed.
1712   *
1713   * @since 2.1.0
1714   *
1715   * @see load_template()
1716   *
1717   * @param bool $for_comments True for the comment feed, false for normal feed.
1718   */
1719  function do_feed_atom( $for_comments ) {
1720      if ( $for_comments ) {
1721          load_template( ABSPATH . WPINC . '/feed-atom-comments.php' );
1722      } else {
1723          load_template( ABSPATH . WPINC . '/feed-atom.php' );
1724      }
1725  }
1726  
1727  /**
1728   * Displays the default robots.txt file content.
1729   *
1730   * @since 2.1.0
1731   * @since 5.3.0 Remove the "Disallow: /" output if search engine visibility is
1732   *              discouraged in favor of robots meta HTML tag via wp_robots_no_robots()
1733   *              filter callback.
1734   */
1735  function do_robots() {
1736      if ( ! headers_sent() ) {
1737          header( 'Content-Type: text/plain; charset=utf-8' );
1738      }
1739  
1740      /**
1741       * Fires when displaying the robots.txt file.
1742       *
1743       * @since 2.1.0
1744       */
1745      do_action( 'do_robotstxt' );
1746  
1747      $output = "User-agent: *\n";
1748      $public = (bool) get_option( 'blog_public' );
1749  
1750      $output .= 'Disallow: ' . wp_parse_url( admin_url(), PHP_URL_PATH ) . "\n";
1751      $output .= 'Allow: ' . wp_parse_url( admin_url( 'admin-ajax.php' ), PHP_URL_PATH ) . "\n";
1752  
1753      /**
1754       * Filters the robots.txt output.
1755       *
1756       * @since 3.0.0
1757       *
1758       * @param string $output The robots.txt output.
1759       * @param bool   $public Whether the site is considered "public".
1760       */
1761      echo apply_filters( 'robots_txt', $output, $public );
1762  }
1763  
1764  /**
1765   * Displays the favicon.ico file content.
1766   *
1767   * @since 5.4.0
1768   *
1769   * @return never
1770   */
1771  function do_favicon() {
1772      /**
1773       * Fires when serving the favicon.ico file.
1774       *
1775       * @since 5.4.0
1776       */
1777      do_action( 'do_faviconico' );
1778  
1779      wp_redirect( get_site_icon_url( 32, includes_url( 'images/w-logo-gray-white-bg.png' ) ) );
1780      exit;
1781  }
1782  
1783  /**
1784   * Determines whether WordPress is already installed.
1785   *
1786   * The cache will be checked first. If you have a cache plugin, which saves
1787   * the cache values, then this will work. If you use the default WordPress
1788   * cache, and the database goes away, then you might have problems.
1789   *
1790   * Checks for the 'siteurl' option for whether WordPress is installed.
1791   *
1792   * For more information on this and similar theme functions, check out
1793   * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1794   * Conditional Tags} article in the Theme Developer Handbook.
1795   *
1796   * @since 2.1.0
1797   *
1798   * @global wpdb $wpdb WordPress database abstraction object.
1799   *
1800   * @return bool Whether the site is already installed.
1801   */
1802  function is_blog_installed() {
1803      global $wpdb;
1804  
1805      /*
1806       * Check cache first. If options table goes away and we have true
1807       * cached, oh well.
1808       */
1809      if ( wp_cache_get( 'is_blog_installed' ) ) {
1810          return true;
1811      }
1812  
1813      $suppress = $wpdb->suppress_errors();
1814  
1815      if ( ! wp_installing() ) {
1816          $alloptions = wp_load_alloptions();
1817      }
1818  
1819      // If siteurl is not set to autoload, check it specifically.
1820      if ( ! isset( $alloptions['siteurl'] ) ) {
1821          $installed = $wpdb->get_var( "SELECT option_value FROM $wpdb->options WHERE option_name = 'siteurl'" );
1822      } else {
1823          $installed = $alloptions['siteurl'];
1824      }
1825  
1826      $wpdb->suppress_errors( $suppress );
1827  
1828      $installed = ! empty( $installed );
1829      wp_cache_set( 'is_blog_installed', $installed );
1830  
1831      if ( $installed ) {
1832          return true;
1833      }
1834  
1835      // If visiting repair.php, return true and let it take over.
1836      if ( defined( 'WP_REPAIRING' ) ) {
1837          return true;
1838      }
1839  
1840      $suppress = $wpdb->suppress_errors();
1841  
1842      /*
1843       * Loop over the WP tables. If none exist, then scratch installation is allowed.
1844       * If one or more exist, suggest table repair since we got here because the
1845       * options table could not be accessed.
1846       */
1847      $wp_tables = $wpdb->tables();
1848      foreach ( $wp_tables as $table ) {
1849          // The existence of custom user tables shouldn't suggest an unwise state or prevent a clean installation.
1850          if ( defined( 'CUSTOM_USER_TABLE' ) && CUSTOM_USER_TABLE === $table ) {
1851              continue;
1852          }
1853  
1854          if ( defined( 'CUSTOM_USER_META_TABLE' ) && CUSTOM_USER_META_TABLE === $table ) {
1855              continue;
1856          }
1857  
1858          $described_table = $wpdb->get_results( "DESCRIBE $table;" );
1859          if (
1860              ( ! $described_table && empty( $wpdb->last_error ) ) ||
1861              ( is_array( $described_table ) && 0 === count( $described_table ) )
1862          ) {
1863              continue;
1864          }
1865  
1866          // One or more tables exist. This is not good.
1867  
1868          wp_load_translations_early();
1869  
1870          // Die with a DB error.
1871          $wpdb->error = sprintf(
1872              /* translators: %s: Database repair URL. */
1873              __( 'One or more database tables are unavailable. The database may need to be <a href="%s">repaired</a>.' ),
1874              'maint/repair.php?referrer=is_blog_installed'
1875          );
1876  
1877          dead_db();
1878      }
1879  
1880      $wpdb->suppress_errors( $suppress );
1881  
1882      wp_cache_set( 'is_blog_installed', false );
1883  
1884      return false;
1885  }
1886  
1887  /**
1888   * Retrieves URL with nonce added to URL query.
1889   *
1890   * @since 2.0.4
1891   *
1892   * @param string     $actionurl URL to add nonce action.
1893   * @param int|string $action    Optional. Nonce action name. Default -1.
1894   * @param string     $name      Optional. Nonce name. Default '_wpnonce'.
1895   * @return string Escaped URL with nonce action added.
1896   */
1897  function wp_nonce_url( $actionurl, $action = -1, $name = '_wpnonce' ) {
1898      $actionurl = str_replace( '&amp;', '&', $actionurl );
1899      return esc_html( add_query_arg( $name, wp_create_nonce( $action ), $actionurl ) );
1900  }
1901  
1902  /**
1903   * Retrieves or display nonce hidden field for forms.
1904   *
1905   * The nonce field is used to validate that the contents of the form came from
1906   * the location on the current site and not somewhere else. The nonce does not
1907   * offer absolute protection, but should protect against most cases. It is very
1908   * important to use nonce field in forms.
1909   *
1910   * The $action and $name are optional, but if you want to have better security,
1911   * it is strongly suggested to set those two parameters. It is easier to just
1912   * call the function without any parameters, because validation of the nonce
1913   * doesn't require any parameters, but since crackers know what the default is
1914   * it won't be difficult for them to find a way around your nonce and cause
1915   * damage.
1916   *
1917   * The input name will be whatever $name value you gave. The input value will be
1918   * the nonce creation value.
1919   *
1920   * @since 2.0.4
1921   *
1922   * @param int|string $action  Optional. Action name. Default -1.
1923   * @param string     $name    Optional. Nonce name. Default '_wpnonce'.
1924   * @param bool       $referer Optional. Whether to set the referer field for validation. Default true.
1925   * @param bool       $display Optional. Whether to display or return hidden form field. Default true.
1926   * @return string Nonce field HTML markup.
1927   */
1928  function wp_nonce_field( $action = -1, $name = '_wpnonce', $referer = true, $display = true ) {
1929      $name        = esc_attr( $name );
1930      $nonce_field = '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . wp_create_nonce( $action ) . '" />';
1931  
1932      if ( $referer ) {
1933          $nonce_field .= wp_referer_field( false );
1934      }
1935  
1936      if ( $display ) {
1937          echo $nonce_field;
1938      }
1939  
1940      return $nonce_field;
1941  }
1942  
1943  /**
1944   * Retrieves or displays referer hidden field for forms.
1945   *
1946   * The referer link is the current Request URI from the server super global. The
1947   * input name is '_wp_http_referer', in case you wanted to check manually.
1948   *
1949   * @since 2.0.4
1950   *
1951   * @param bool $display Optional. Whether to echo or return the referer field. Default true.
1952   * @return string Referer field HTML markup.
1953   */
1954  function wp_referer_field( $display = true ) {
1955      $request_url   = remove_query_arg( '_wp_http_referer' );
1956      $referer_field = '<input type="hidden" name="_wp_http_referer" value="' . esc_url( $request_url ) . '" />';
1957  
1958      if ( $display ) {
1959          echo $referer_field;
1960      }
1961  
1962      return $referer_field;
1963  }
1964  
1965  /**
1966   * Retrieves or displays original referer hidden field for forms.
1967   *
1968   * The input name is '_wp_original_http_referer' and will be either the same
1969   * value of wp_referer_field(), if that was posted already or it will be the
1970   * current page, if it doesn't exist.
1971   *
1972   * @since 2.0.4
1973   *
1974   * @param bool   $display      Optional. Whether to echo the original http referer. Default true.
1975   * @param string $jump_back_to Optional. Can be 'previous' or page you want to jump back to.
1976   *                             Default 'current'.
1977   * @return string Original referer field.
1978   */
1979  function wp_original_referer_field( $display = true, $jump_back_to = 'current' ) {
1980      $ref = wp_get_original_referer();
1981  
1982      if ( ! $ref ) {
1983          $ref = ( 'previous' === $jump_back_to ) ? wp_get_referer() : wp_unslash( $_SERVER['REQUEST_URI'] );
1984      }
1985  
1986      $orig_referer_field = '<input type="hidden" name="_wp_original_http_referer" value="' . esc_attr( $ref ) . '" />';
1987  
1988      if ( $display ) {
1989          echo $orig_referer_field;
1990      }
1991  
1992      return $orig_referer_field;
1993  }
1994  
1995  /**
1996   * Retrieves referer from '_wp_http_referer' or HTTP referer.
1997   *
1998   * If it's the same as the current request URL, will return false.
1999   *
2000   * @since 2.0.4
2001   *
2002   * @return string|false Referer URL on success, false on failure.
2003   */
2004  function wp_get_referer() {
2005      // Return early if called before wp_validate_redirect() is defined.
2006      if ( ! function_exists( 'wp_validate_redirect' ) ) {
2007          return false;
2008      }
2009  
2010      $ref = wp_get_raw_referer();
2011  
2012      if ( $ref && wp_unslash( $_SERVER['REQUEST_URI'] ) !== $ref
2013          && home_url() . wp_unslash( $_SERVER['REQUEST_URI'] ) !== $ref
2014      ) {
2015          return wp_validate_redirect( $ref, false );
2016      }
2017  
2018      return false;
2019  }
2020  
2021  /**
2022   * Retrieves unvalidated referer from the '_wp_http_referer' URL query variable or the HTTP referer.
2023   *
2024   * If the value of the '_wp_http_referer' URL query variable is not a string then it will be ignored.
2025   *
2026   * Do not use for redirects, use wp_get_referer() instead.
2027   *
2028   * @since 4.5.0
2029   *
2030   * @return string|false Referer URL on success, false on failure.
2031   */
2032  function wp_get_raw_referer() {
2033      if ( ! empty( $_REQUEST['_wp_http_referer'] ) && is_string( $_REQUEST['_wp_http_referer'] ) ) {
2034          return wp_unslash( $_REQUEST['_wp_http_referer'] );
2035      } elseif ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
2036          return wp_unslash( $_SERVER['HTTP_REFERER'] );
2037      }
2038  
2039      return false;
2040  }
2041  
2042  /**
2043   * Retrieves original referer that was posted, if it exists.
2044   *
2045   * @since 2.0.4
2046   *
2047   * @return string|false Original referer URL on success, false on failure.
2048   */
2049  function wp_get_original_referer() {
2050      // Return early if called before wp_validate_redirect() is defined.
2051      if ( ! function_exists( 'wp_validate_redirect' ) ) {
2052          return false;
2053      }
2054  
2055      if ( ! empty( $_REQUEST['_wp_original_http_referer'] ) ) {
2056          return wp_validate_redirect( wp_unslash( $_REQUEST['_wp_original_http_referer'] ), false );
2057      }
2058  
2059      return false;
2060  }
2061  
2062  /**
2063   * Recursive directory creation based on full path.
2064   *
2065   * Will attempt to set permissions on folders.
2066   *
2067   * @since 2.0.1
2068   *
2069   * @param string $target Full path to attempt to create.
2070   * @return bool Whether the path was created. True if path already exists.
2071   */
2072  function wp_mkdir_p( $target ) {
2073      $wrapper = null;
2074  
2075      // Strip the protocol.
2076      if ( wp_is_stream( $target ) ) {
2077          list( $wrapper, $target ) = explode( '://', $target, 2 );
2078      }
2079  
2080      // From php.net/mkdir user contributed notes.
2081      $target = str_replace( '//', '/', $target );
2082  
2083      // Put the wrapper back on the target.
2084      if ( null !== $wrapper ) {
2085          $target = $wrapper . '://' . $target;
2086      }
2087  
2088      /*
2089       * Safe mode fails with a trailing slash under certain PHP versions.
2090       * Use rtrim() instead of untrailingslashit to avoid formatting.php dependency.
2091       */
2092      $target = rtrim( $target, '/' );
2093      if ( empty( $target ) ) {
2094          $target = '/';
2095      }
2096  
2097      if ( file_exists( $target ) ) {
2098          return @is_dir( $target );
2099      }
2100  
2101      // Do not allow path traversals.
2102      if ( str_contains( $target, '../' ) || str_contains( $target, '..' . DIRECTORY_SEPARATOR ) ) {
2103          return false;
2104      }
2105  
2106      // We need to find the permissions of the parent folder that exists and inherit that.
2107      $target_parent = dirname( $target );
2108      while ( '.' !== $target_parent && ! is_dir( $target_parent ) && dirname( $target_parent ) !== $target_parent ) {
2109          $target_parent = dirname( $target_parent );
2110      }
2111  
2112      // Get the permission bits.
2113      $stat = @stat( $target_parent );
2114      if ( $stat ) {
2115          $dir_perms = $stat['mode'] & 0007777;
2116      } else {
2117          $dir_perms = 0777;
2118      }
2119  
2120      if ( @mkdir( $target, $dir_perms, true ) ) {
2121  
2122          /*
2123           * If a umask is set that modifies $dir_perms, we'll have to re-set
2124           * the $dir_perms correctly with chmod()
2125           */
2126          if ( ( $dir_perms & ~umask() ) !== $dir_perms ) {
2127              $folder_parts = explode( '/', substr( $target, strlen( $target_parent ) + 1 ) );
2128              for ( $i = 1, $c = count( $folder_parts ); $i <= $c; $i++ ) {
2129                  chmod( $target_parent . '/' . implode( '/', array_slice( $folder_parts, 0, $i ) ), $dir_perms );
2130              }
2131          }
2132  
2133          return true;
2134      }
2135  
2136      return false;
2137  }
2138  
2139  /**
2140   * Tests if a given filesystem path is absolute.
2141   *
2142   * For example, '/foo/bar', or 'c:\windows'.
2143   *
2144   * @since 2.5.0
2145   *
2146   * @param string $path File path.
2147   * @return bool True if path is absolute, false is not absolute.
2148   *
2149   * @phpstan-return ( $path is non-falsy-string ? bool : false )
2150   */
2151  function path_is_absolute( $path ) {
2152      /*
2153       * Check to see if the path is a stream and check to see if its an actual
2154       * path or file as realpath() does not support stream wrappers.
2155       */
2156      if ( wp_is_stream( $path ) && ( is_dir( $path ) || is_file( $path ) ) ) {
2157          return true;
2158      }
2159  
2160      /*
2161       * This is definitive if true but fails if $path does not exist or contains
2162       * a symbolic link.
2163       */
2164      if ( realpath( $path ) === $path ) {
2165          return true;
2166      }
2167  
2168      if ( strlen( $path ) === 0 || '.' === $path[0] ) {
2169          return false;
2170      }
2171  
2172      // Windows allows absolute paths like this.
2173      if ( preg_match( '#^[a-zA-Z]:\\\\#', $path ) ) {
2174          return true;
2175      }
2176  
2177      // A path starting with / or \ is absolute; anything else is relative.
2178      return ( '/' === $path[0] || '\\' === $path[0] );
2179  }
2180  
2181  /**
2182   * Joins two filesystem paths together.
2183   *
2184   * For example, 'give me $path relative to $base'. If the $path is absolute,
2185   * then it the full path is returned.
2186   *
2187   * @since 2.5.0
2188   *
2189   * @param string $base Base path.
2190   * @param string $path Path relative to $base.
2191   * @return string The path with the base or absolute path.
2192   */
2193  function path_join( $base, $path ) {
2194      if ( path_is_absolute( $path ) ) {
2195          return $path;
2196      }
2197  
2198      return rtrim( $base, '/' ) . '/' . $path;
2199  }
2200  
2201  /**
2202   * Normalizes a filesystem path.
2203   *
2204   * On windows systems, replaces backslashes with forward slashes
2205   * and forces upper-case drive letters.
2206   * Allows for two leading slashes for Windows network shares, but
2207   * ensures that all other duplicate slashes are reduced to a single.
2208   *
2209   * @since 3.9.0
2210   * @since 4.4.0 Ensures upper-case drive letters on Windows systems.
2211   * @since 4.5.0 Allows for Windows network shares.
2212   * @since 4.9.7 Allows for PHP file wrappers.
2213   * @since 7.0.0 Uses a static cache to store normalized paths.
2214   *
2215   * @param string $path Path to normalize.
2216   * @return string Normalized path.
2217   */
2218  function wp_normalize_path( $path ): string {
2219      $path = (string) $path;
2220  
2221      static $cache = array();
2222      if ( isset( $cache[ $path ] ) ) {
2223          return $cache[ $path ];
2224      }
2225  
2226      $original_path = $path;
2227      $wrapper       = '';
2228  
2229      if ( wp_is_stream( $path ) ) {
2230          list( $wrapper, $path ) = explode( '://', $path, 2 );
2231  
2232          $wrapper .= '://';
2233      }
2234  
2235      // Standardize all paths to use '/'.
2236      $path = str_replace( '\\', '/', $path );
2237  
2238      // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
2239      $path = (string) preg_replace( '|(?<=.)/+|', '/', $path );
2240  
2241      // Windows paths should uppercase the drive letter.
2242      if ( ':' === substr( $path, 1, 1 ) ) {
2243          $path = ucfirst( $path );
2244      }
2245  
2246      $cache[ $original_path ] = $wrapper . $path;
2247      return $cache[ $original_path ];
2248  }
2249  
2250  /**
2251   * Determines a writable directory for temporary files.
2252   *
2253   * Function's preference is the return value of `sys_get_temp_dir()`,
2254   * followed by the `upload_tmp_dir` value from `php.ini`, followed by `WP_CONTENT_DIR`,
2255   * before finally defaulting to `/tmp/`.
2256   *
2257   * Note that `sys_get_temp_dir()` honors the `TMPDIR` environment variable.
2258   *
2259   * In the event that this function does not find a writable location,
2260   * it may be overridden by the `WP_TEMP_DIR` constant in your `wp-config.php` file.
2261   *
2262   * @since 2.5.0
2263   *
2264   * @return string Writable temporary directory.
2265   */
2266  function get_temp_dir() {
2267      static $temp = '';
2268      if ( defined( 'WP_TEMP_DIR' ) ) {
2269          return trailingslashit( WP_TEMP_DIR );
2270      }
2271  
2272      if ( $temp ) {
2273          return trailingslashit( $temp );
2274      }
2275  
2276      if ( function_exists( 'sys_get_temp_dir' ) ) {
2277          $temp = sys_get_temp_dir();
2278          if ( @is_dir( $temp ) && wp_is_writable( $temp ) ) {
2279              return trailingslashit( $temp );
2280          }
2281      }
2282  
2283      $temp = ini_get( 'upload_tmp_dir' );
2284      if ( @is_dir( $temp ) && wp_is_writable( $temp ) ) {
2285          return trailingslashit( $temp );
2286      }
2287  
2288      $temp = WP_CONTENT_DIR . '/';
2289      if ( is_dir( $temp ) && wp_is_writable( $temp ) ) {
2290          return $temp;
2291      }
2292  
2293      return '/tmp/';
2294  }
2295  
2296  /**
2297   * Determines if a directory is writable.
2298   *
2299   * This function is used to work around certain ACL issues in PHP primarily
2300   * affecting Windows Servers.
2301   *
2302   * @since 3.6.0
2303   *
2304   * @see win_is_writable()
2305   *
2306   * @param string $path Path to check for write-ability.
2307   * @return bool Whether the path is writable.
2308   */
2309  function wp_is_writable( $path ) {
2310      if ( 'Windows' === PHP_OS_FAMILY ) {
2311          return win_is_writable( $path );
2312      }
2313  
2314      return @is_writable( $path );
2315  }
2316  
2317  /**
2318   * Workaround for Windows bug in is_writable() function
2319   *
2320   * PHP has issues with Windows ACL's for determine if a
2321   * directory is writable or not, this works around them by
2322   * checking the ability to open files rather than relying
2323   * upon PHP to interpret the OS ACL.
2324   *
2325   * @since 2.8.0
2326   *
2327   * @see https://bugs.php.net/bug.php?id=27609
2328   * @see https://bugs.php.net/bug.php?id=30931
2329   *
2330   * @param string $path Windows path to check for write-ability.
2331   * @return bool Whether the path is writable.
2332   */
2333  function win_is_writable( $path ) {
2334      if ( '/' === $path[ strlen( $path ) - 1 ] ) {
2335          // If it looks like a directory, check a random file within the directory.
2336          return win_is_writable( $path . uniqid( mt_rand() ) . '.tmp' );
2337      } elseif ( is_dir( $path ) ) {
2338          // If it's a directory (and not a file), check a random file within the directory.
2339          return win_is_writable( $path . '/' . uniqid( mt_rand() ) . '.tmp' );
2340      }
2341  
2342      // Check tmp file for read/write capabilities.
2343      $should_delete_tmp_file = ! file_exists( $path );
2344  
2345      $f = @fopen( $path, 'a' );
2346      if ( false === $f ) {
2347          return false;
2348      }
2349      fclose( $f );
2350  
2351      if ( $should_delete_tmp_file ) {
2352          unlink( $path );
2353      }
2354  
2355      return true;
2356  }
2357  
2358  /**
2359   * Retrieves uploads directory information.
2360   *
2361   * Same as wp_upload_dir() but "light weight" as it doesn't attempt to create the uploads directory.
2362   * Intended for use in themes, when only 'basedir' and 'baseurl' are needed, generally in all cases
2363   * when not uploading files.
2364   *
2365   * @since 4.5.0
2366   *
2367   * @see wp_upload_dir()
2368   *
2369   * @return array See wp_upload_dir() for description.
2370   * @phpstan-return array{
2371   *                     path: non-empty-string,
2372   *                     url: non-empty-string,
2373   *                     subdir: string,
2374   *                     basedir: non-empty-string,
2375   *                     baseurl: non-empty-string,
2376   *                     error: non-empty-string|false,
2377   *                 }
2378   */
2379  function wp_get_upload_dir() {
2380      return wp_upload_dir( null, false );
2381  }
2382  
2383  /**
2384   * Returns an array containing the current upload directory's path and URL.
2385   *
2386   * Checks the 'upload_path' option, which should be from the web root folder,
2387   * and if it isn't empty it will be used. If it is empty, then the path will be
2388   * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
2389   * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
2390   *
2391   * The upload URL path is set either by the 'upload_url_path' option or by using
2392   * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
2393   *
2394   * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
2395   * the administration settings panel), then the time will be used. The format
2396   * will be year first and then month.
2397   *
2398   * If the path couldn't be created, then an error will be returned with the key
2399   * 'error' containing the error message. The error suggests that the parent
2400   * directory is not writable by the server.
2401   *
2402   * @since 2.0.0
2403   * @uses _wp_upload_dir()
2404   *
2405   * @param string|null $time          Optional. Time formatted in 'yyyy/mm'. Default null.
2406   * @param bool        $create_dir    Optional. Whether to check and create the uploads directory.
2407   *                                   Default true for backward compatibility.
2408   * @param bool        $refresh_cache Optional. Whether to refresh the cache. Default false.
2409   * @return array {
2410   *     Array of information about the upload directory.
2411   *
2412   *     @type string       $path    Base directory and subdirectory or full path to upload directory.
2413   *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
2414   *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
2415   *     @type string       $basedir Path without subdir.
2416   *     @type string       $baseurl URL path without subdir.
2417   *     @type string|false $error   False or error message.
2418   * }
2419   * @phpstan-return array{
2420   *                     path: non-empty-string,
2421   *                     url: non-empty-string,
2422   *                     subdir: string,
2423   *                     basedir: non-empty-string,
2424   *                     baseurl: non-empty-string,
2425   *                     error: non-empty-string|false,
2426   *                 }
2427   */
2428  function wp_upload_dir( $time = null, $create_dir = true, $refresh_cache = false ) {
2429      static $cache = array(), $tested_paths = array();
2430  
2431      $key = sprintf( '%d-%s', get_current_blog_id(), (string) $time );
2432  
2433      if ( $refresh_cache || empty( $cache[ $key ] ) ) {
2434          $cache[ $key ] = _wp_upload_dir( $time );
2435      }
2436  
2437      /**
2438       * Filters the uploads directory data.
2439       *
2440       * @since 2.0.0
2441       *
2442       * @param array $uploads {
2443       *     Array of information about the upload directory.
2444       *
2445       *     @type string       $path    Base directory and subdirectory or full path to upload directory.
2446       *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
2447       *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
2448       *     @type string       $basedir Path without subdir.
2449       *     @type string       $baseurl URL path without subdir.
2450       *     @type string|false $error   False or error message.
2451       * }
2452       */
2453      $uploads = apply_filters( 'upload_dir', $cache[ $key ] );
2454  
2455      if ( $create_dir ) {
2456          $path = $uploads['path'];
2457  
2458          if ( array_key_exists( $path, $tested_paths ) ) {
2459              $uploads['error'] = $tested_paths[ $path ];
2460          } else {
2461              if ( ! wp_mkdir_p( $path ) ) {
2462                  if ( str_starts_with( $uploads['basedir'], ABSPATH ) ) {
2463                      $error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
2464                  } else {
2465                      $error_path = wp_basename( $uploads['basedir'] ) . $uploads['subdir'];
2466                  }
2467  
2468                  $uploads['error'] = sprintf(
2469                      /* translators: %s: Directory path. */
2470                      __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
2471                      esc_html( $error_path )
2472                  );
2473              }
2474  
2475              $tested_paths[ $path ] = $uploads['error'];
2476          }
2477      }
2478  
2479      return $uploads;
2480  }
2481  
2482  /**
2483   * A non-filtered, non-cached version of wp_upload_dir() that doesn't check the path.
2484   *
2485   * @since 4.5.0
2486   * @access private
2487   *
2488   * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null.
2489   * @return array See wp_upload_dir()
2490   * @phpstan-return array{
2491   *                     path: non-empty-string,
2492   *                     url: non-empty-string,
2493   *                     subdir: string,
2494   *                     basedir: non-empty-string,
2495   *                     baseurl: non-empty-string,
2496   *                     error: false,
2497   *                 }
2498   */
2499  function _wp_upload_dir( $time = null ) {
2500      $siteurl     = get_option( 'siteurl' );
2501      $upload_path = trim( get_option( 'upload_path' ) );
2502  
2503      if ( empty( $upload_path ) || 'wp-content/uploads' === $upload_path ) {
2504          $dir = WP_CONTENT_DIR . '/uploads';
2505      } elseif ( ! str_starts_with( $upload_path, ABSPATH ) ) {
2506          // $dir is absolute, $upload_path is (maybe) relative to ABSPATH.
2507          $dir = path_join( ABSPATH, $upload_path );
2508      } else {
2509          $dir = $upload_path;
2510      }
2511  
2512      $url = get_option( 'upload_url_path' );
2513      if ( ! $url ) {
2514          if ( empty( $upload_path ) || ( 'wp-content/uploads' === $upload_path ) || ( $upload_path === $dir ) ) {
2515              $url = WP_CONTENT_URL . '/uploads';
2516          } else {
2517              $url = trailingslashit( $siteurl ) . $upload_path;
2518          }
2519      }
2520  
2521      /*
2522       * Honor the value of UPLOADS. This happens as long as ms-files rewriting is disabled.
2523       * We also sometimes obey UPLOADS when rewriting is enabled -- see the next block.
2524       */
2525      if ( defined( 'UPLOADS' ) && ! ( is_multisite() && get_site_option( 'ms_files_rewriting' ) ) ) {
2526          $dir = ABSPATH . UPLOADS;
2527          $url = trailingslashit( $siteurl ) . UPLOADS;
2528      }
2529  
2530      // If multisite (and if not the main site in a post-MU network).
2531      if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
2532  
2533          if ( ! get_site_option( 'ms_files_rewriting' ) ) {
2534              /*
2535               * If ms-files rewriting is disabled (networks created post-3.5), it is fairly
2536               * straightforward: Append sites/%d if we're not on the main site (for post-MU
2537               * networks). (The extra directory prevents a four-digit ID from conflicting with
2538               * a year-based directory for the main site. But if a MU-era network has disabled
2539               * ms-files rewriting manually, they don't need the extra directory, as they never
2540               * had wp-content/uploads for the main site.)
2541               */
2542  
2543              if ( defined( 'MULTISITE' ) ) {
2544                  $ms_dir = '/sites/' . get_current_blog_id();
2545              } else {
2546                  $ms_dir = '/' . get_current_blog_id();
2547              }
2548  
2549              $dir .= $ms_dir;
2550              $url .= $ms_dir;
2551  
2552          } elseif ( defined( 'UPLOADS' ) && ! ms_is_switched() ) {
2553              /*
2554               * Handle the old-form ms-files.php rewriting if the network still has that enabled.
2555               * When ms-files rewriting is enabled, then we only listen to UPLOADS when:
2556               * 1) We are not on the main site in a post-MU network, as wp-content/uploads is used
2557               *    there, and
2558               * 2) We are not switched, as ms_upload_constants() hardcodes these constants to reflect
2559               *    the original blog ID.
2560               *
2561               * Rather than UPLOADS, we actually use BLOGUPLOADDIR if it is set, as it is absolute.
2562               * (And it will be set, see ms_upload_constants().) Otherwise, UPLOADS can be used, as
2563               * as it is relative to ABSPATH. For the final piece: when UPLOADS is used with ms-files
2564               * rewriting in multisite, the resulting URL is /files. (#WP22702 for background.)
2565               */
2566  
2567              if ( defined( 'BLOGUPLOADDIR' ) ) {
2568                  $dir = untrailingslashit( BLOGUPLOADDIR );
2569              } else {
2570                  $dir = ABSPATH . UPLOADS;
2571              }
2572              $url = trailingslashit( $siteurl ) . 'files';
2573          }
2574      }
2575  
2576      $basedir = $dir;
2577      $baseurl = $url;
2578  
2579      $subdir = '';
2580      if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
2581          // Generate the yearly and monthly directories.
2582          if ( ! $time ) {
2583              $time = current_time( 'mysql' );
2584          }
2585          $y      = substr( $time, 0, 4 );
2586          $m      = substr( $time, 5, 2 );
2587          $subdir = "/$y/$m";
2588      }
2589  
2590      $dir .= $subdir;
2591      $url .= $subdir;
2592  
2593      return array(
2594          'path'    => $dir,
2595          'url'     => $url,
2596          'subdir'  => $subdir,
2597          'basedir' => $basedir,
2598          'baseurl' => $baseurl,
2599          'error'   => false,
2600      );
2601  }
2602  
2603  /**
2604   * Gets a filename that is sanitized and unique for the given directory.
2605   *
2606   * If the filename is not unique, then a number will be added to the filename
2607   * before the extension, and will continue adding numbers until the filename
2608   * is unique.
2609   *
2610   * The callback function allows the caller to use their own method to create
2611   * unique file names. If defined, the callback should take three arguments:
2612   * - directory, base filename, and extension - and return a unique filename.
2613   *
2614   * @since 2.5.0
2615   *
2616   * @param string   $dir                      Directory.
2617   * @param string   $filename                 File name.
2618   * @param callable $unique_filename_callback Callback. Default null.
2619   * @return string New filename, if given wasn't unique.
2620   */
2621  function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) {
2622      // Sanitize the file name before we begin processing.
2623      $filename = sanitize_file_name( $filename );
2624  
2625      // Initialize vars used in the wp_unique_filename filter.
2626      $number        = '';
2627      $alt_filenames = array();
2628  
2629      // Separate the filename into a name and extension.
2630      $ext  = pathinfo( $filename, PATHINFO_EXTENSION );
2631      $name = pathinfo( $filename, PATHINFO_BASENAME );
2632  
2633      if ( $ext ) {
2634          $ext = '.' . $ext;
2635      }
2636  
2637      // Edge case: if file is named '.ext', treat as an empty name.
2638      if ( $name === $ext ) {
2639          $name = '';
2640      }
2641  
2642      /*
2643       * Increment the file number until we have a unique file to save in $dir.
2644       * Use callback if supplied.
2645       */
2646      if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) {
2647          $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext );
2648      } else {
2649          $fname = pathinfo( $filename, PATHINFO_FILENAME );
2650  
2651          // Always append a number to file names that can potentially match image sub-size file names.
2652          if ( $fname && preg_match( '/-(?:\d+x\d+|scaled|rotated)$/', $fname ) ) {
2653              $number = 1;
2654  
2655              // At this point the file name may not be unique. This is tested below and the $number is incremented.
2656              $filename = str_replace( "{$fname}{$ext}", "{$fname}-{$number}{$ext}", $filename );
2657          }
2658  
2659          /*
2660           * Get the mime type. Uploaded files were already checked with wp_check_filetype_and_ext()
2661           * in _wp_handle_upload(). Using wp_check_filetype() would be sufficient here.
2662           */
2663          $file_type = wp_check_filetype( $filename );
2664          $mime_type = $file_type['type'];
2665  
2666          $is_image    = ( ! empty( $mime_type ) && str_starts_with( $mime_type, 'image/' ) );
2667          $upload_dir  = wp_get_upload_dir();
2668          $lc_filename = null;
2669  
2670          $lc_ext = strtolower( $ext );
2671          $_dir   = trailingslashit( $dir );
2672  
2673          /*
2674           * If the extension is uppercase add an alternate file name with lowercase extension.
2675           * Both need to be tested for uniqueness as the extension will be changed to lowercase
2676           * for better compatibility with different filesystems. Fixes an inconsistency in WP < 2.9
2677           * where uppercase extensions were allowed but image sub-sizes were created with
2678           * lowercase extensions.
2679           */
2680          if ( $ext && $lc_ext !== $ext ) {
2681              $lc_filename = preg_replace( '|' . preg_quote( $ext ) . '$|', $lc_ext, $filename );
2682          }
2683  
2684          /*
2685           * Increment the number added to the file name if there are any files in $dir
2686           * whose names match one of the possible name variations.
2687           */
2688          while ( file_exists( $_dir . $filename ) || ( $lc_filename && file_exists( $_dir . $lc_filename ) ) ) {
2689              $new_number = (int) $number + 1;
2690  
2691              if ( $lc_filename ) {
2692                  $lc_filename = str_replace(
2693                      array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ),
2694                      "-{$new_number}{$lc_ext}",
2695                      $lc_filename
2696                  );
2697              }
2698  
2699              if ( '' === "{$number}{$ext}" ) {
2700                  $filename = "{$filename}-{$new_number}";
2701              } else {
2702                  $filename = str_replace(
2703                      array( "-{$number}{$ext}", "{$number}{$ext}" ),
2704                      "-{$new_number}{$ext}",
2705                      $filename
2706                  );
2707              }
2708  
2709              $number = $new_number;
2710          }
2711  
2712          // Change the extension to lowercase if needed.
2713          if ( $lc_filename ) {
2714              $filename = $lc_filename;
2715          }
2716  
2717          /*
2718           * Prevent collisions with existing file names that contain dimension-like strings
2719           * (whether they are subsizes or originals uploaded prior to #42437).
2720           */
2721  
2722          $files = array();
2723          $count = 10000;
2724  
2725          // The (resized) image files would have name and extension, and will be in the uploads dir.
2726          if ( $name && $ext && @is_dir( $dir ) && str_contains( $dir, $upload_dir['basedir'] ) ) {
2727              /**
2728               * Filters the file list used for calculating a unique filename for a newly added file.
2729               *
2730               * Returning an array from the filter will effectively short-circuit retrieval
2731               * from the filesystem and return the passed value instead.
2732               *
2733               * @since 5.5.0
2734               *
2735               * @param array|null $files    The list of files to use for filename comparisons.
2736               *                             Default null (to retrieve the list from the filesystem).
2737               * @param string     $dir      The directory for the new file.
2738               * @param string     $filename The proposed filename for the new file.
2739               */
2740              $files = apply_filters( 'pre_wp_unique_filename_file_list', null, $dir, $filename );
2741  
2742              if ( null === $files ) {
2743                  // List of all files and directories contained in $dir.
2744                  $files = @scandir( $dir );
2745              }
2746  
2747              if ( ! empty( $files ) ) {
2748                  // Remove "dot" dirs.
2749                  $files = array_diff( $files, array( '.', '..' ) );
2750              }
2751  
2752              if ( ! empty( $files ) ) {
2753                  $count = count( $files );
2754  
2755                  /*
2756                   * Ensure this never goes into infinite loop as it uses pathinfo() and regex in the check,
2757                   * but string replacement for the changes.
2758                   */
2759                  $i = 0;
2760  
2761                  while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) {
2762                      $new_number = (int) $number + 1;
2763  
2764                      // If $ext is uppercase it was replaced with the lowercase version after the previous loop.
2765                      $filename = str_replace(
2766                          array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ),
2767                          "-{$new_number}{$lc_ext}",
2768                          $filename
2769                      );
2770  
2771                      $number = $new_number;
2772                      ++$i;
2773                  }
2774              }
2775          }
2776  
2777          /*
2778           * Check if an image will be converted after uploading or some existing image sub-size file names may conflict
2779           * when regenerated. If yes, ensure the new file name will be unique and will produce unique sub-sizes.
2780           */
2781          if ( $is_image ) {
2782              $output_formats = wp_get_image_editor_output_format( $_dir . $filename, $mime_type );
2783              $alt_types      = array();
2784  
2785              if ( ! empty( $output_formats[ $mime_type ] ) ) {
2786                  // The image will be converted to this format/mime type.
2787                  $alt_mime_type = $output_formats[ $mime_type ];
2788  
2789                  // Other types of images whose names may conflict if their sub-sizes are regenerated.
2790                  $alt_types   = array_keys( array_intersect( $output_formats, array( $mime_type, $alt_mime_type ) ) );
2791                  $alt_types[] = $alt_mime_type;
2792              } elseif ( ! empty( $output_formats ) ) {
2793                  $alt_types = array_keys( array_intersect( $output_formats, array( $mime_type ) ) );
2794              }
2795  
2796              // Remove duplicates and the original mime type. It will be added later if needed.
2797              $alt_types = array_unique( array_diff( $alt_types, array( $mime_type ) ) );
2798  
2799              foreach ( $alt_types as $alt_type ) {
2800                  $alt_ext = wp_get_default_extension_for_mime_type( $alt_type );
2801  
2802                  if ( ! $alt_ext ) {
2803                      continue;
2804                  }
2805  
2806                  $alt_ext      = ".{$alt_ext}";
2807                  $alt_filename = preg_replace( '|' . preg_quote( $lc_ext ) . '$|', $alt_ext, $filename );
2808  
2809                  $alt_filenames[ $alt_ext ] = $alt_filename;
2810              }
2811  
2812              if ( ! empty( $alt_filenames ) ) {
2813                  /*
2814                   * Add the original filename. It needs to be checked again
2815                   * together with the alternate filenames when $number is incremented.
2816                   */
2817                  $alt_filenames[ $lc_ext ] = $filename;
2818  
2819                  // Ensure no infinite loop.
2820                  $i = 0;
2821  
2822                  while ( $i <= $count && _wp_check_alternate_file_names( $alt_filenames, $_dir, $files ) ) {
2823                      $new_number = (int) $number + 1;
2824  
2825                      foreach ( $alt_filenames as $alt_ext => $alt_filename ) {
2826                          $alt_filenames[ $alt_ext ] = str_replace(
2827                              array( "-{$number}{$alt_ext}", "{$number}{$alt_ext}" ),
2828                              "-{$new_number}{$alt_ext}",
2829                              $alt_filename
2830                          );
2831                      }
2832  
2833                      /*
2834                       * Also update the $number in (the output) $filename.
2835                       * If the extension was uppercase it was already replaced with the lowercase version.
2836                       */
2837                      $filename = str_replace(
2838                          array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ),
2839                          "-{$new_number}{$lc_ext}",
2840                          $filename
2841                      );
2842  
2843                      $number = $new_number;
2844                      ++$i;
2845                  }
2846              }
2847          }
2848      }
2849  
2850      /**
2851       * Filters the result when generating a unique file name.
2852       *
2853       * @since 4.5.0
2854       * @since 5.8.1 The `$alt_filenames` and `$number` parameters were added.
2855       *
2856       * @param string        $filename                 Unique file name.
2857       * @param string        $ext                      File extension. Example: ".png".
2858       * @param string        $dir                      Directory path.
2859       * @param callable|null $unique_filename_callback Callback function that generates the unique file name.
2860       * @param string[]      $alt_filenames            Array of alternate file names that were checked for collisions.
2861       * @param int|string    $number                   The highest number that was used to make the file name unique
2862       *                                                or an empty string if unused.
2863       */
2864      return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number );
2865  }
2866  
2867  /**
2868   * Helper function to test if each of an array of file names could conflict with existing files.
2869   *
2870   * @since 5.8.1
2871   * @access private
2872   *
2873   * @param string[] $filenames Array of file names to check.
2874   * @param string   $dir       The directory containing the files.
2875   * @param array    $files     An array of existing files in the directory. May be empty.
2876   * @return bool True if the tested file name could match an existing file, false otherwise.
2877   */
2878  function _wp_check_alternate_file_names( $filenames, $dir, $files ) {
2879      foreach ( $filenames as $filename ) {
2880          if ( file_exists( $dir . $filename ) ) {
2881              return true;
2882          }
2883  
2884          if ( ! empty( $files ) && _wp_check_existing_file_names( $filename, $files ) ) {
2885              return true;
2886          }
2887      }
2888  
2889      return false;
2890  }
2891  
2892  /**
2893   * Helper function to check if a file name could match an existing image sub-size file name.
2894   *
2895   * @since 5.3.1
2896   * @access private
2897   *
2898   * @param string $filename The file name to check.
2899   * @param array  $files    An array of existing files in the directory.
2900   * @return bool True if the tested file name could match an existing file, false otherwise.
2901   */
2902  function _wp_check_existing_file_names( $filename, $files ) {
2903      $fname = pathinfo( $filename, PATHINFO_FILENAME );
2904      $ext   = pathinfo( $filename, PATHINFO_EXTENSION );
2905  
2906      // Edge case, file names like `.ext`.
2907      if ( empty( $fname ) ) {
2908          return false;
2909      }
2910  
2911      if ( $ext ) {
2912          $ext = ".$ext";
2913      }
2914  
2915      $regex = '/^' . preg_quote( $fname ) . '-(?:\d+x\d+|scaled|rotated)' . preg_quote( $ext ) . '$/i';
2916  
2917      foreach ( $files as $file ) {
2918          if ( preg_match( $regex, $file ) ) {
2919              return true;
2920          }
2921      }
2922  
2923      return false;
2924  }
2925  
2926  /**
2927   * Creates a file in the upload folder with given content.
2928   *
2929   * If there is an error, then the key 'error' will exist with the error message.
2930   * If success, then the key 'file' will have the unique file path, the 'url' key
2931   * will have the link to the new file. and the 'error' key will be set to false.
2932   *
2933   * This function will not move an uploaded file to the upload folder. It will
2934   * create a new file with the content in $bits parameter. If you move the upload
2935   * file, read the content of the uploaded file, and then you can give the
2936   * filename and content to this function, which will add it to the upload
2937   * folder.
2938   *
2939   * The permissions will be set on the new file automatically by this function.
2940   *
2941   * @since 2.0.0
2942   *
2943   * @param string      $name       Filename.
2944   * @param null|string $deprecated Not used. Set to null.
2945   * @param string      $bits       File content
2946   * @param string|null $time       Optional. Time formatted in 'yyyy/mm'. Default null.
2947   * @return array {
2948   *     Information about the newly-uploaded file.
2949   *
2950   *     @type string       $file  Optional. Filename of the newly-uploaded file. Not set if there has been an error.
2951   *     @type string       $url   Optional. URL of the uploaded file. Not set if there has been an error.
2952   *     @type string|false $type  Optional. File type, or false if the file doesn't match a mime type.
2953   *                               Not set if there has been an error.
2954   *     @type string|false $error Error message, if there has been an error.
2955   * }
2956   * @phpstan-return array{ file: non-empty-string, url: non-empty-string, type: string|false, error: false }
2957   *                |array{ error: string, ... }
2958   */
2959  function wp_upload_bits( $name, $deprecated, $bits, $time = null ) {
2960      if ( ! empty( $deprecated ) ) {
2961          _deprecated_argument( __FUNCTION__, '2.0.0' );
2962      }
2963  
2964      if ( empty( $name ) ) {
2965          return array( 'error' => __( 'Empty filename' ) );
2966      }
2967  
2968      $wp_filetype = wp_check_filetype( $name );
2969      if ( ! $wp_filetype['ext'] && ! current_user_can( 'unfiltered_upload' ) ) {
2970          return array( 'error' => __( 'Sorry, you are not allowed to upload this file type.' ) );
2971      }
2972  
2973      $upload = wp_upload_dir( $time );
2974  
2975      if ( false !== $upload['error'] ) {
2976          return $upload;
2977      }
2978  
2979      /**
2980       * Filters whether to treat the upload bits as an error.
2981       *
2982       * Returning a non-array from the filter will effectively short-circuit preparing the upload bits
2983       * and return that value instead. An error message should be returned as a string.
2984       *
2985       * @since 3.0.0
2986       *
2987       * @param array|string $upload_bits_error An array of upload bits data, or error message to return.
2988       */
2989      $upload_bits_error = apply_filters(
2990          'wp_upload_bits',
2991          array(
2992              'name' => $name,
2993              'bits' => $bits,
2994              'time' => $time,
2995          )
2996      );
2997      if ( ! is_array( $upload_bits_error ) ) {
2998          $upload['error'] = $upload_bits_error;
2999          return $upload;
3000      }
3001  
3002      $filename = wp_unique_filename( $upload['path'], $name );
3003  
3004      $new_file = $upload['path'] . "/$filename";
3005      if ( ! wp_mkdir_p( dirname( $new_file ) ) ) {
3006          if ( str_starts_with( $upload['basedir'], ABSPATH ) ) {
3007              $error_path = str_replace( ABSPATH, '', $upload['basedir'] ) . $upload['subdir'];
3008          } else {
3009              $error_path = wp_basename( $upload['basedir'] ) . $upload['subdir'];
3010          }
3011  
3012          $message = sprintf(
3013              /* translators: %s: Directory path. */
3014              __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
3015              $error_path
3016          );
3017          return array( 'error' => $message );
3018      }
3019  
3020      $ifp = @fopen( $new_file, 'wb' );
3021      if ( ! $ifp ) {
3022          return array(
3023              /* translators: %s: File name. */
3024              'error' => sprintf( __( 'Could not write file %s' ), $new_file ),
3025          );
3026      }
3027  
3028      fwrite( $ifp, $bits );
3029      fclose( $ifp );
3030      clearstatcache();
3031  
3032      // Set correct file permissions.
3033      $stat  = @ stat( dirname( $new_file ) );
3034      $perms = $stat['mode'] & 0007777;
3035      $perms = $perms & 0000666;
3036      chmod( $new_file, $perms );
3037      clearstatcache();
3038  
3039      // Compute the URL.
3040      $url = $upload['url'] . "/$filename";
3041  
3042      if ( is_multisite() ) {
3043          clean_dirsize_cache( $new_file );
3044      }
3045  
3046      /** This filter is documented in wp-admin/includes/file.php */
3047      return apply_filters(
3048          'wp_handle_upload',
3049          array(
3050              'file'  => $new_file,
3051              'url'   => $url,
3052              'type'  => $wp_filetype['type'],
3053              'error' => false,
3054          ),
3055          'sideload'
3056      );
3057  }
3058  
3059  /**
3060   * Retrieves the file type based on the extension name.
3061   *
3062   * @since 2.5.0
3063   *
3064   * @param string $ext The extension to search.
3065   * @return string|null The file type, example: audio, video, document, spreadsheet, etc.
3066   */
3067  function wp_ext2type( $ext ) {
3068      $ext = strtolower( $ext );
3069  
3070      $ext2type = wp_get_ext_types();
3071  
3072      return array_find_key(
3073          $ext2type,
3074          fn( $exts ) => in_array( $ext, $exts, true )
3075      );
3076  }
3077  
3078  /**
3079   * Returns the first matched extension for the mime type, as mapped from wp_get_mime_types().
3080   *
3081   * @since 5.8.1
3082   *
3083   * @param string $mime_type The mime type to search.
3084   * @return string|false The first matching file extension, or false if no extensions are found
3085   *                      for the given mime type.
3086   */
3087  function wp_get_default_extension_for_mime_type( $mime_type ) {
3088      $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) );
3089  
3090      if ( empty( $extensions[0] ) ) {
3091          return false;
3092      }
3093  
3094      return $extensions[0];
3095  }
3096  
3097  /**
3098   * Retrieves the file type from the file name.
3099   *
3100   * You can optionally define the mime array, if needed.
3101   *
3102   * @since 2.0.4
3103   *
3104   * @param string        $filename File name or path.
3105   * @param string[]|null $mimes    Optional. Array of allowed mime types keyed by their file extension regex.
3106   *                                Defaults to the result of get_allowed_mime_types().
3107   * @return array {
3108   *     Values for the extension and mime type.
3109   *
3110   *     @type string|false $ext  File extension, or false if the file doesn't match a mime type.
3111   *     @type string|false $type File mime type, or false if the file doesn't match a mime type.
3112   * }
3113   */
3114  function wp_check_filetype( $filename, $mimes = null ) {
3115      if ( empty( $mimes ) ) {
3116          $mimes = get_allowed_mime_types();
3117      }
3118      $type = false;
3119      $ext  = false;
3120  
3121      foreach ( $mimes as $ext_preg => $mime_match ) {
3122          $ext_preg = '!\.(' . $ext_preg . ')$!i';
3123          if ( preg_match( $ext_preg, $filename, $ext_matches ) ) {
3124              $type = $mime_match;
3125              $ext  = $ext_matches[1];
3126              break;
3127          }
3128      }
3129  
3130      return compact( 'ext', 'type' );
3131  }
3132  
3133  /**
3134   * Attempts to determine the real file type of a file.
3135   *
3136   * If unable to, the file name extension will be used to determine type.
3137   *
3138   * If it's determined that the extension does not match the file's real type,
3139   * then the "proper_filename" value will be set with a proper filename and extension.
3140   *
3141   * Currently this function only supports renaming images validated via wp_get_image_mime().
3142   *
3143   * @since 3.0.0
3144   *
3145   * @param string        $file     Full path to the file.
3146   * @param string        $filename The name of the file (may differ from $file due to $file being
3147   *                                in a tmp directory).
3148   * @param string[]|null $mimes    Optional. Array of allowed mime types keyed by their file extension regex.
3149   *                                Defaults to the result of get_allowed_mime_types().
3150   * @return array {
3151   *     Values for the extension, mime type, and corrected filename.
3152   *
3153   *     @type string|false $ext             File extension, or false if the file doesn't match a mime type.
3154   *     @type string|false $type            File mime type, or false if the file doesn't match a mime type.
3155   *     @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
3156   * }
3157   */
3158  function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
3159      $proper_filename = false;
3160  
3161      // Do basic extension validation and MIME mapping.
3162      $wp_filetype = wp_check_filetype( $filename, $mimes );
3163      $ext         = $wp_filetype['ext'];
3164      $type        = $wp_filetype['type'];
3165  
3166      // We can't do any further validation without a file to work with.
3167      if ( ! file_exists( $file ) ) {
3168          return compact( 'ext', 'type', 'proper_filename' );
3169      }
3170  
3171      $real_mime = false;
3172  
3173      // Validate image types.
3174      if ( $type && str_starts_with( $type, 'image/' ) ) {
3175  
3176          // Attempt to figure out what type of image it actually is.
3177          $real_mime = wp_get_image_mime( $file );
3178  
3179          $heic_images_extensions = array(
3180              'heif',
3181              'heics',
3182              'heifs',
3183          );
3184  
3185          if ( $real_mime && ( $real_mime !== $type || in_array( $ext, $heic_images_extensions, true ) ) ) {
3186              /**
3187               * Filters the list mapping image mime types to their respective extensions.
3188               *
3189               * @since 3.0.0
3190               *
3191               * @param array $mime_to_ext Array of image mime types and their matching extensions.
3192               */
3193              $mime_to_ext = apply_filters(
3194                  'getimagesize_mimes_to_exts',
3195                  array(
3196                      'image/jpeg'          => 'jpg',
3197                      'image/png'           => 'png',
3198                      'image/gif'           => 'gif',
3199                      'image/bmp'           => 'bmp',
3200                      'image/tiff'          => 'tif',
3201                      'image/webp'          => 'webp',
3202                      'image/avif'          => 'avif',
3203  
3204                      /*
3205                       * In theory there are/should be file extensions that correspond to the
3206                       * mime types: .heif, .heics and .heifs. However it seems that HEIC images
3207                       * with any of the mime types commonly have a .heic file extension.
3208                       * Seems keeping the status quo here is best for compatibility.
3209                       */
3210                      'image/heic'          => 'heic',
3211                      'image/heif'          => 'heic',
3212                      'image/heic-sequence' => 'heic',
3213                      'image/heif-sequence' => 'heic',
3214                  )
3215              );
3216  
3217              // Replace whatever is after the last period in the filename with the correct extension.
3218              if ( ! empty( $mime_to_ext[ $real_mime ] ) ) {
3219                  $filename_parts = explode( '.', $filename );
3220  
3221                  array_pop( $filename_parts );
3222                  $filename_parts[] = $mime_to_ext[ $real_mime ];
3223                  $new_filename     = implode( '.', $filename_parts );
3224  
3225                  if ( $new_filename !== $filename ) {
3226                      $proper_filename = $new_filename; // Mark that it changed.
3227                  }
3228  
3229                  // Redefine the extension / MIME.
3230                  $wp_filetype = wp_check_filetype( $new_filename, $mimes );
3231                  $ext         = $wp_filetype['ext'];
3232                  $type        = $wp_filetype['type'];
3233              } else {
3234                  // Reset $real_mime and try validating again.
3235                  $real_mime = false;
3236              }
3237          }
3238      }
3239  
3240      // Validate files that didn't get validated during previous checks.
3241      if ( $type && ! $real_mime && extension_loaded( 'fileinfo' ) ) {
3242          $finfo     = finfo_open( FILEINFO_MIME_TYPE );
3243          $real_mime = finfo_file( $finfo, $file );
3244  
3245          if ( PHP_VERSION_ID < 80100 ) { // finfo_close() has no effect as of PHP 8.1.
3246              finfo_close( $finfo );
3247          }
3248  
3249          $google_docs_types = array(
3250              'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3251              'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3252          );
3253  
3254          foreach ( $google_docs_types as $google_docs_type ) {
3255              /*
3256               * finfo_file() can return duplicate mime type for Google docs,
3257               * this conditional reduces it to a single instance.
3258               *
3259               * @see https://bugs.php.net/bug.php?id=77784
3260               * @see https://core.trac.wordpress.org/ticket/57898
3261               */
3262              if ( 2 === substr_count( $real_mime, $google_docs_type ) ) {
3263                  $real_mime = $google_docs_type;
3264              }
3265          }
3266  
3267          // fileinfo often misidentifies obscure files as one of these types.
3268          $nonspecific_types = array(
3269              'application/octet-stream',
3270              'application/encrypted',
3271              'application/CDFV2-encrypted',
3272              'application/zip',
3273          );
3274  
3275          /*
3276           * If $real_mime doesn't match the content type we're expecting from the file's extension,
3277           * we need to do some additional vetting. Media types and those listed in $nonspecific_types are
3278           * allowed some leeway, but anything else must exactly match the real content type.
3279           */
3280          if ( in_array( $real_mime, $nonspecific_types, true ) ) {
3281              // File is a non-specific binary type. That's ok if it's a type that generally tends to be binary.
3282              if ( ! in_array( substr( $type, 0, strcspn( $type, '/' ) ), array( 'application', 'video', 'audio' ), true ) ) {
3283                  $type = false;
3284                  $ext  = false;
3285              }
3286          } elseif ( str_starts_with( $real_mime, 'video/' ) || str_starts_with( $real_mime, 'audio/' ) ) {
3287              /*
3288               * For these types, only the major type must match the real value.
3289               * This means that common mismatches are forgiven: application/vnd.apple.numbers is often misidentified as application/zip,
3290               * and some media files are commonly named with the wrong extension (.mov instead of .mp4)
3291               */
3292              if ( substr( $real_mime, 0, strcspn( $real_mime, '/' ) ) !== substr( $type, 0, strcspn( $type, '/' ) ) ) {
3293                  $type = false;
3294                  $ext  = false;
3295              }
3296          } elseif ( 'text/plain' === $real_mime ) {
3297              // A few common file types are occasionally detected as text/plain; allow those.
3298              if ( ! in_array(
3299                  $type,
3300                  array(
3301                      'text/plain',
3302                      'text/csv',
3303                      'application/csv',
3304                      'text/richtext',
3305                      'text/tsv',
3306                      'text/vtt',
3307                  ),
3308                  true
3309              )
3310              ) {
3311                  $type = false;
3312                  $ext  = false;
3313              }
3314          } elseif ( 'application/csv' === $real_mime ) {
3315              // Special casing for CSV files.
3316              if ( ! in_array(
3317                  $type,
3318                  array(
3319                      'text/csv',
3320                      'text/plain',
3321                      'application/csv',
3322                  ),
3323                  true
3324              )
3325              ) {
3326                  $type = false;
3327                  $ext  = false;
3328              }
3329          } elseif ( 'text/rtf' === $real_mime ) {
3330              // Special casing for RTF files.
3331              if ( ! in_array(
3332                  $type,
3333                  array(
3334                      'text/rtf',
3335                      'text/plain',
3336                      'application/rtf',
3337                  ),
3338                  true
3339              )
3340              ) {
3341                  $type = false;
3342                  $ext  = false;
3343              }
3344          } else {
3345              if ( $type !== $real_mime ) {
3346                  /*
3347                   * Everything else including image/* and application/*:
3348                   * If the real content type doesn't match the file extension, assume it's dangerous.
3349                   */
3350                  $type = false;
3351                  $ext  = false;
3352              }
3353          }
3354      }
3355  
3356      // The mime type must be allowed.
3357      if ( $type ) {
3358          $allowed = get_allowed_mime_types();
3359  
3360          if ( ! in_array( $type, $allowed, true ) ) {
3361              $type = false;
3362              $ext  = false;
3363          }
3364      }
3365  
3366      /**
3367       * Filters the "real" file type of the given file.
3368       *
3369       * @since 3.0.0
3370       * @since 5.1.0 The `$real_mime` parameter was added.
3371       *
3372       * @param array         $wp_check_filetype_and_ext {
3373       *     Values for the extension, mime type, and corrected filename.
3374       *
3375       *     @type string|false $ext             File extension, or false if the file doesn't match a mime type.
3376       *     @type string|false $type            File mime type, or false if the file doesn't match a mime type.
3377       *     @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined.
3378       * }
3379       * @param string        $file                      Full path to the file.
3380       * @param string        $filename                  The name of the file (may differ from $file due to
3381       *                                                 $file being in a tmp directory).
3382       * @param string[]|null $mimes                     Array of mime types keyed by their file extension regex, or null if
3383       *                                                 none were provided.
3384       * @param string|false  $real_mime                 The actual mime type or false if the type cannot be determined.
3385       */
3386      return apply_filters( 'wp_check_filetype_and_ext', compact( 'ext', 'type', 'proper_filename' ), $file, $filename, $mimes, $real_mime );
3387  }
3388  
3389  /**
3390   * Returns the real mime type of an image file.
3391   *
3392   * This depends on exif_imagetype() or getimagesize() to determine real mime types.
3393   *
3394   * @since 4.7.1
3395   * @since 5.8.0 Added support for WebP images.
3396   * @since 6.5.0 Added support for AVIF images.
3397   * @since 6.7.0 Added support for HEIC images.
3398   *
3399   * @param string $file Full path to the file.
3400   * @return string|false The actual mime type or false if the type cannot be determined.
3401   */
3402  function wp_get_image_mime( $file ) {
3403      /*
3404       * Use exif_imagetype() to check the mimetype if available or fall back to
3405       * getimagesize() if exif isn't available. If either function throws an Exception
3406       * we assume the file could not be validated.
3407       */
3408      try {
3409          if ( is_callable( 'exif_imagetype' ) ) {
3410              $imagetype = exif_imagetype( $file );
3411              $mime      = ( $imagetype ) ? image_type_to_mime_type( $imagetype ) : false;
3412          } elseif ( function_exists( 'getimagesize' ) ) {
3413              // Don't silence errors when in debug mode, unless running unit tests.
3414              if ( defined( 'WP_DEBUG' ) && WP_DEBUG && ! defined( 'WP_RUN_CORE_TESTS' ) ) {
3415                  // Not using wp_getimagesize() here to avoid an infinite loop.
3416                  $imagesize = getimagesize( $file );
3417              } else {
3418                  $imagesize = @getimagesize( $file );
3419              }
3420  
3421              $mime = $imagesize['mime'] ?? false;
3422          } else {
3423              $mime = false;
3424          }
3425  
3426          if ( false !== $mime ) {
3427              return $mime;
3428          }
3429  
3430          $magic = file_get_contents( $file, false, null, 0, 12 );
3431  
3432          if ( false === $magic ) {
3433              return false;
3434          }
3435  
3436          /*
3437           * Add WebP fallback detection when image library doesn't support WebP.
3438           * Note: detection values come from LibWebP, see
3439           * https://github.com/webmproject/libwebp/blob/master/imageio/image_dec.c#L30
3440           */
3441          $magic = bin2hex( $magic );
3442          if (
3443              // RIFF.
3444              ( str_starts_with( $magic, '52494646' ) ) &&
3445              // WEBP.
3446              ( 16 === strpos( $magic, '57454250' ) )
3447          ) {
3448              $mime = 'image/webp';
3449          }
3450  
3451          /**
3452           * Add AVIF fallback detection when image library doesn't support AVIF.
3453           *
3454           * Detection based on section 4.3.1 File-type box definition of the ISO/IEC 14496-12
3455           * specification and the AV1-AVIF spec, see https://aomediacodec.github.io/av1-avif/v1.1.0.html#brands.
3456           */
3457  
3458          // Divide the header string into 4 byte groups.
3459          $magic = str_split( $magic, 8 );
3460  
3461          if ( isset( $magic[1] ) && isset( $magic[2] ) && 'ftyp' === hex2bin( $magic[1] ) ) {
3462              if ( 'avif' === hex2bin( $magic[2] ) || 'avis' === hex2bin( $magic[2] ) ) {
3463                  $mime = 'image/avif';
3464              } elseif ( 'heic' === hex2bin( $magic[2] ) ) {
3465                  $mime = 'image/heic';
3466              } elseif ( 'heif' === hex2bin( $magic[2] ) ) {
3467                  $mime = 'image/heif';
3468              } else {
3469                  /*
3470                   * HEIC/HEIF images and image sequences/animations may have other strings here
3471                   * like mif1, msf1, etc. For now fall back to using finfo_file() to detect these.
3472                   */
3473                  if ( extension_loaded( 'fileinfo' ) ) {
3474                      $fileinfo  = finfo_open( FILEINFO_MIME_TYPE );
3475                      $mime_type = finfo_file( $fileinfo, $file );
3476  
3477                      if ( PHP_VERSION_ID < 80100 ) { // finfo_close() has no effect as of PHP 8.1.
3478                          finfo_close( $fileinfo );
3479                      }
3480  
3481                      if ( wp_is_heic_image_mime_type( $mime_type ) ) {
3482                          $mime = $mime_type;
3483                      }
3484                  }
3485              }
3486          }
3487      } catch ( Exception $e ) {
3488          $mime = false;
3489      }
3490  
3491      return $mime;
3492  }
3493  
3494  /**
3495   * Retrieves the list of mime types and file extensions.
3496   *
3497   * @since 3.5.0
3498   * @since 4.2.0 Support was added for GIMP (.xcf) files.
3499   * @since 4.9.2 Support was added for Flac (.flac) files.
3500   * @since 4.9.6 Support was added for AAC (.aac) files.
3501   * @since 6.8.0 Support was added for `audio/x-wav`.
3502   *
3503   * @return string[] Array of mime types keyed by the file extension regex corresponding to those types.
3504   */
3505  function wp_get_mime_types() {
3506      /**
3507       * Filters the list of mime types and file extensions.
3508       *
3509       * This filter should be used to add, not remove, mime types. To remove
3510       * mime types, use the {@see 'upload_mimes'} filter.
3511       *
3512       * @since 3.5.0
3513       *
3514       * @param string[] $wp_get_mime_types Mime types keyed by the file extension regex
3515       *                                    corresponding to those types.
3516       */
3517      return apply_filters(
3518          'mime_types',
3519          array(
3520              // Image formats.
3521              'jpg|jpeg|jpe'                 => 'image/jpeg',
3522              'gif'                          => 'image/gif',
3523              'png'                          => 'image/png',
3524              'bmp'                          => 'image/bmp',
3525              'tiff|tif'                     => 'image/tiff',
3526              'webp'                         => 'image/webp',
3527              'avif'                         => 'image/avif',
3528              'ico'                          => 'image/x-icon',
3529  
3530              // TODO: Needs improvement. All images with the following mime types seem to have .heic file extension.
3531              'heic'                         => 'image/heic',
3532              'heif'                         => 'image/heif',
3533              'heics'                        => 'image/heic-sequence',
3534              'heifs'                        => 'image/heif-sequence',
3535  
3536              // Video formats.
3537              'asf|asx'                      => 'video/x-ms-asf',
3538              'wmv'                          => 'video/x-ms-wmv',
3539              'wmx'                          => 'video/x-ms-wmx',
3540              'wm'                           => 'video/x-ms-wm',
3541              'avi'                          => 'video/avi',
3542              'divx'                         => 'video/divx',
3543              'flv'                          => 'video/x-flv',
3544              'mov|qt'                       => 'video/quicktime',
3545              'mpeg|mpg|mpe'                 => 'video/mpeg',
3546              'mp4|m4v'                      => 'video/mp4',
3547              'ogv'                          => 'video/ogg',
3548              'webm'                         => 'video/webm',
3549              'mkv'                          => 'video/x-matroska',
3550              '3gp|3gpp'                     => 'video/3gpp',  // Can also be audio.
3551              '3g2|3gp2'                     => 'video/3gpp2', // Can also be audio.
3552              // Text formats.
3553              'txt|asc|c|cc|h|srt'           => 'text/plain',
3554              'csv'                          => 'text/csv',
3555              'tsv'                          => 'text/tab-separated-values',
3556              'ics'                          => 'text/calendar',
3557              'rtx'                          => 'text/richtext',
3558              'css'                          => 'text/css',
3559              'htm|html'                     => 'text/html',
3560              'vtt'                          => 'text/vtt',
3561              'dfxp'                         => 'application/ttaf+xml',
3562              // Audio formats.
3563              'mp3|m4a|m4b'                  => 'audio/mpeg',
3564              'aac'                          => 'audio/aac',
3565              'ra|ram'                       => 'audio/x-realaudio',
3566              'wav|x-wav'                    => 'audio/wav',
3567              'ogg|oga'                      => 'audio/ogg',
3568              'flac'                         => 'audio/flac',
3569              'mid|midi'                     => 'audio/midi',
3570              'wma'                          => 'audio/x-ms-wma',
3571              'wax'                          => 'audio/x-ms-wax',
3572              'mka'                          => 'audio/x-matroska',
3573              // Misc application formats.
3574              'rtf'                          => 'application/rtf',
3575              'js'                           => 'application/javascript',
3576              'pdf'                          => 'application/pdf',
3577              'swf'                          => 'application/x-shockwave-flash',
3578              'class'                        => 'application/java',
3579              'tar'                          => 'application/x-tar',
3580              'zip'                          => 'application/zip',
3581              'gz|gzip'                      => 'application/x-gzip',
3582              'rar'                          => 'application/rar',
3583              '7z'                           => 'application/x-7z-compressed',
3584              'exe'                          => 'application/x-msdownload',
3585              'psd'                          => 'application/octet-stream',
3586              'xcf'                          => 'application/octet-stream',
3587              // MS Office formats.
3588              'doc'                          => 'application/msword',
3589              'pot|pps|ppt'                  => 'application/vnd.ms-powerpoint',
3590              'wri'                          => 'application/vnd.ms-write',
3591              'xla|xls|xlt|xlw'              => 'application/vnd.ms-excel',
3592              'mdb'                          => 'application/vnd.ms-access',
3593              'mpp'                          => 'application/vnd.ms-project',
3594              'docx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3595              'docm'                         => 'application/vnd.ms-word.document.macroEnabled.12',
3596              'dotx'                         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3597              'dotm'                         => 'application/vnd.ms-word.template.macroEnabled.12',
3598              'xlsx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3599              'xlsm'                         => 'application/vnd.ms-excel.sheet.macroEnabled.12',
3600              'xlsb'                         => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3601              'xltx'                         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3602              'xltm'                         => 'application/vnd.ms-excel.template.macroEnabled.12',
3603              'xlam'                         => 'application/vnd.ms-excel.addin.macroEnabled.12',
3604              'pptx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3605              'pptm'                         => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
3606              'ppsx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3607              'ppsm'                         => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
3608              'potx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3609              'potm'                         => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
3610              'ppam'                         => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
3611              'sldx'                         => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3612              'sldm'                         => 'application/vnd.ms-powerpoint.slide.macroEnabled.12',
3613              'onetoc|onetoc2|onetmp|onepkg' => 'application/onenote',
3614              'oxps'                         => 'application/oxps',
3615              'xps'                          => 'application/vnd.ms-xpsdocument',
3616              // OpenOffice formats.
3617              'odt'                          => 'application/vnd.oasis.opendocument.text',
3618              'odp'                          => 'application/vnd.oasis.opendocument.presentation',
3619              'ods'                          => 'application/vnd.oasis.opendocument.spreadsheet',
3620              'odg'                          => 'application/vnd.oasis.opendocument.graphics',
3621              'odc'                          => 'application/vnd.oasis.opendocument.chart',
3622              'odb'                          => 'application/vnd.oasis.opendocument.database',
3623              'odf'                          => 'application/vnd.oasis.opendocument.formula',
3624              // WordPerfect formats.
3625              'wp|wpd'                       => 'application/wordperfect',
3626              // iWork formats.
3627              'key'                          => 'application/vnd.apple.keynote',
3628              'numbers'                      => 'application/vnd.apple.numbers',
3629              'pages'                        => 'application/vnd.apple.pages',
3630          )
3631      );
3632  }
3633  
3634  /**
3635   * Retrieves the list of common file extensions and their types.
3636   *
3637   * @since 4.6.0
3638   *
3639   * @return array[] Multi-dimensional array of file extensions types keyed by the type of file.
3640   */
3641  function wp_get_ext_types() {
3642  
3643      /**
3644       * Filters file type based on the extension name.
3645       *
3646       * @since 2.5.0
3647       *
3648       * @see wp_ext2type()
3649       *
3650       * @param array[] $ext2type Multi-dimensional array of file extensions types keyed by the type of file.
3651       */
3652      return apply_filters(
3653          'ext2type',
3654          array(
3655              'image'       => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'heif', 'webp', 'avif' ),
3656              'audio'       => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ),
3657              'video'       => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ),
3658              'document'    => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ),
3659              'spreadsheet' => array( 'numbers', 'ods', 'xls', 'xlsx', 'xlsm', 'xlsb' ),
3660              'interactive' => array( 'swf', 'key', 'ppt', 'pptx', 'pptm', 'pps', 'ppsx', 'ppsm', 'sldx', 'sldm', 'odp' ),
3661              'text'        => array( 'asc', 'csv', 'tsv', 'txt' ),
3662              'archive'     => array( 'bz2', 'cab', 'dmg', 'gz', 'rar', 'sea', 'sit', 'sqx', 'tar', 'tgz', 'zip', '7z' ),
3663              'code'        => array( 'css', 'htm', 'html', 'php', 'js' ),
3664          )
3665      );
3666  }
3667  
3668  /**
3669   * Wrapper for PHP filesize with filters and casting the result as an integer.
3670   *
3671   * @since 6.0.0
3672   * @since 7.1.0 The return value is now ensured to always be greater than or equal to zero.
3673   *
3674   * @link https://www.php.net/manual/en/function.filesize.php
3675   *
3676   * @param string $path Path to the file.
3677   * @return int The size of the file in bytes, or 0 in the event of an error.
3678   * @phpstan-return non-negative-int
3679   */
3680  function wp_filesize( $path ): int {
3681      /**
3682       * Filters the result of wp_filesize() before the file_exists() PHP function is run.
3683       *
3684       * @since 6.0.0
3685       * @since 7.1.0 Negative values are now ignored, being treated the same as null. Numeric values are cast to integers.
3686       *
3687       * @param null|int $size The unfiltered value. Returning a non-negative number from the callback bypasses the filesize call.
3688       * @param string   $path Path to the file.
3689       */
3690      $size = apply_filters( 'pre_wp_filesize', null, $path );
3691      if ( is_numeric( $size ) ) {
3692          $size = (int) $size;
3693      }
3694      if ( is_int( $size ) && $size >= 0 ) {
3695          return $size;
3696      }
3697  
3698      $size = file_exists( $path ) ? (int) filesize( $path ) : 0;
3699  
3700      /**
3701       * Filters the size of the file.
3702       *
3703       * @since 6.0.0
3704       * @since 7.1.0 The return value is now always zero or greater. Numeric values are cast to integers.
3705       *
3706       * @param int    $size The result of PHP filesize on the file.
3707       * @param string $path Path to the file.
3708       */
3709      $size = apply_filters( 'wp_filesize', $size, $path );
3710      if ( is_numeric( $size ) ) {
3711          $size = (int) $size;
3712      } else {
3713          $size = 0;
3714      }
3715      return max( 0, $size );
3716  }
3717  
3718  /**
3719   * Retrieves the list of allowed mime types and file extensions.
3720   *
3721   * @since 2.8.6
3722   *
3723   * @param int|WP_User $user Optional. User to check. Defaults to current user.
3724   * @return string[] Array of mime types keyed by the file extension regex corresponding
3725   *                  to those types.
3726   */
3727  function get_allowed_mime_types( $user = null ) {
3728      $t = wp_get_mime_types();
3729  
3730      unset( $t['swf'], $t['exe'] );
3731      if ( function_exists( 'current_user_can' ) ) {
3732          $unfiltered = $user ? user_can( $user, 'unfiltered_html' ) : current_user_can( 'unfiltered_html' );
3733      }
3734  
3735      if ( empty( $unfiltered ) ) {
3736          unset( $t['htm|html'], $t['js'] );
3737      }
3738  
3739      /**
3740       * Filters the list of allowed mime types and file extensions.
3741       *
3742       * @since 2.0.0
3743       *
3744       * @param array            $t    Mime types keyed by the file extension regex corresponding to those types.
3745       * @param int|WP_User|null $user User ID, User object or null if not provided (indicates current user).
3746       */
3747      return apply_filters( 'upload_mimes', $t, $user );
3748  }
3749  
3750  /**
3751   * Displays "Are You Sure" message to confirm the action being taken.
3752   *
3753   * If the action has the nonce explain message, then it will be displayed
3754   * along with the "Are you sure?" message.
3755   *
3756   * @since 2.0.4
3757   *
3758   * @param string $action The nonce action.
3759   * @return never
3760   */
3761  function wp_nonce_ays( $action ) {
3762      // Default title and response code.
3763      $title         = __( 'An error occurred.' );
3764      $response_code = 403;
3765  
3766      if ( 'log-out' === $action ) {
3767          $title = sprintf(
3768              /* translators: %s: Site title. */
3769              __( 'You are attempting to log out of %s' ),
3770              get_bloginfo( 'name' )
3771          );
3772  
3773          $redirect_to = $_REQUEST['redirect_to'] ?? '';
3774  
3775          $html  = $title;
3776          $html .= '</p><p>';
3777          $html .= sprintf(
3778              /* translators: %s: Logout URL. */
3779              __( 'Do you really want to <a href="%s">log out</a>?' ),
3780              wp_logout_url( $redirect_to )
3781          );
3782      } else {
3783          $html = __( 'The link you followed has expired.' );
3784  
3785          if ( wp_get_referer() ) {
3786              $wp_http_referer = remove_query_arg( 'updated', wp_get_referer() );
3787              $wp_http_referer = wp_validate_redirect( sanitize_url( $wp_http_referer ) );
3788  
3789              $html .= '</p><p>';
3790              $html .= sprintf(
3791                  '<a href="%s">%s</a>',
3792                  esc_url( $wp_http_referer ),
3793                  __( 'Please try again.' )
3794              );
3795          }
3796      }
3797  
3798      wp_die( $html, $title, $response_code );
3799  }
3800  
3801  /**
3802   * Kills WordPress execution and displays HTML page with an error message.
3803   *
3804   * This function complements the `die()` PHP function. The difference is that
3805   * HTML will be displayed to the user. It is recommended to use this function
3806   * only when the execution should not continue any further. It is not recommended
3807   * to call this function very often, and try to handle as many errors as possible
3808   * silently or more gracefully.
3809   *
3810   * As a shorthand, the desired HTTP response code may be passed as an integer to
3811   * the `$title` parameter (the default title would apply) or the `$args` parameter.
3812   *
3813   * @since 2.0.4
3814   * @since 4.1.0 The `$title` and `$args` parameters were changed to optionally accept
3815   *              an integer to be used as the response code.
3816   * @since 5.1.0 The `$link_url`, `$link_text`, and `$exit` arguments were added.
3817   * @since 5.3.0 The `$charset` argument was added.
3818   * @since 5.5.0 The `$text_direction` argument has a priority over get_language_attributes()
3819   *              in the default handler.
3820   *
3821   * @global WP_Query $wp_query WordPress Query object.
3822   *
3823   * @param string|WP_Error|int $message Optional. Error message. If this is a WP_Error object,
3824   *                                     and not an Ajax or XML-RPC request, the error's messages are used.
3825   *                                     An integer is echoed as the entire response body by legacy Ajax
3826   *                                     handlers, which use -1 for a failed nonce or capability check,
3827   *                                     0 for failure, and 1 for success. Default empty string.
3828   * @param string|int          $title   Optional. Error title. If `$message` is a `WP_Error` object,
3829   *                                     error data with the key 'title' may be used to specify the title.
3830   *                                     If `$title` is an integer, then it is treated as the response code.
3831   *                                     Default empty string.
3832   * @param string|array|int    $args {
3833   *     Optional. Arguments to control behavior. If `$args` is an integer, then it is treated
3834   *     as the response code. Default empty array.
3835   *
3836   *     @type int|null $response       The HTTP response code, or null to send no status header. The Ajax, JSON,
3837   *                                    JSONP and XML handlers all accept null, for backward compatibility.
3838   *                                    Default 200 for Ajax requests, 500 otherwise.
3839   *     @type string   $link_url       A URL to include a link to. Only works in combination with $link_text.
3840   *                                    Default empty string.
3841   *     @type string   $link_text      A label for the link to include. Only works in combination with $link_url.
3842   *                                    Default empty string.
3843   *     @type bool     $back_link      Whether to include a link to go back. Default false.
3844   *     @type string   $text_direction The text direction. This is only useful internally, when WordPress is still
3845   *                                    loading and the site's locale is not set up yet. Accepts 'rtl' and 'ltr'.
3846   *                                    Default is the value of is_rtl().
3847   *     @type string   $charset        Character set of the HTML output. Default 'utf-8'.
3848   *     @type string   $code           Error code to use. Default is 'wp_die', or the main error code if $message
3849   *                                    is a WP_Error.
3850   *     @type bool     $exit           Whether to exit the process after completion. Default true.
3851   * }
3852   * @return void Never returns if `$args['exit']` is true (the default), otherwise returns void.
3853   * @phpstan-param string|WP_Error|int<-1, max> $message
3854   * @phpstan-return ( $args is array{exit: false} ? void : never )
3855   */
3856  function wp_die( $message = '', $title = '', $args = array() ) {
3857      global $wp_query;
3858  
3859      if ( is_int( $args ) ) {
3860          $args = array( 'response' => $args );
3861      } elseif ( is_int( $title ) ) {
3862          $args  = array( 'response' => $title );
3863          $title = '';
3864      }
3865  
3866      if ( wp_doing_ajax() ) {
3867          /**
3868           * Filters the callback for killing WordPress execution for Ajax requests.
3869           *
3870           * @since 3.4.0
3871           *
3872           * @param callable $callback Callback function name.
3873           */
3874          $callback = apply_filters( 'wp_die_ajax_handler', '_ajax_wp_die_handler' );
3875      } elseif ( wp_is_json_request() ) {
3876          /**
3877           * Filters the callback for killing WordPress execution for JSON requests.
3878           *
3879           * @since 5.1.0
3880           *
3881           * @param callable $callback Callback function name.
3882           */
3883          $callback = apply_filters( 'wp_die_json_handler', '_json_wp_die_handler' );
3884      } elseif ( wp_is_serving_rest_request() && wp_is_jsonp_request() ) {
3885          /**
3886           * Filters the callback for killing WordPress execution for JSONP REST requests.
3887           *
3888           * @since 5.2.0
3889           *
3890           * @param callable $callback Callback function name.
3891           */
3892          $callback = apply_filters( 'wp_die_jsonp_handler', '_jsonp_wp_die_handler' );
3893      } elseif ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
3894          /**
3895           * Filters the callback for killing WordPress execution for XML-RPC requests.
3896           *
3897           * @since 3.4.0
3898           *
3899           * @param callable $callback Callback function name.
3900           */
3901          $callback = apply_filters( 'wp_die_xmlrpc_handler', '_xmlrpc_wp_die_handler' );
3902      } elseif ( wp_is_xml_request()
3903          || isset( $wp_query ) &&
3904              ( function_exists( 'is_feed' ) && is_feed()
3905              || function_exists( 'is_comment_feed' ) && is_comment_feed()
3906              || function_exists( 'is_trackback' ) && is_trackback() ) ) {
3907          /**
3908           * Filters the callback for killing WordPress execution for XML requests.
3909           *
3910           * @since 5.2.0
3911           *
3912           * @param callable $callback Callback function name.
3913           */
3914          $callback = apply_filters( 'wp_die_xml_handler', '_xml_wp_die_handler' );
3915      } else {
3916          /**
3917           * Filters the callback for killing WordPress execution for all non-Ajax, non-JSON, non-XML requests.
3918           *
3919           * @since 3.0.0
3920           *
3921           * @param callable $callback Callback function name.
3922           */
3923          $callback = apply_filters( 'wp_die_handler', '_default_wp_die_handler' );
3924      }
3925  
3926      call_user_func( $callback, $message, $title, $args );
3927  }
3928  
3929  /**
3930   * Kills WordPress execution and displays HTML page with an error message.
3931   *
3932   * This is the default handler for wp_die(). If you want a custom one,
3933   * you can override this using the {@see 'wp_die_handler'} filter in wp_die().
3934   *
3935   * @since 3.0.0
3936   * @access private
3937   *
3938   * @param string|WP_Error $message Error message or WP_Error object.
3939   * @param string          $title   Optional. Error title. Default empty string.
3940   * @param string|array    $args    Optional. Arguments to control behavior. Default empty array.
3941   * @phpstan-return ( $args is array{exit: false} ? void : never )
3942   */
3943  function _default_wp_die_handler( $message, $title = '', $args = array() ) {
3944      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
3945  
3946      if ( is_string( $message ) ) {
3947          if ( ! empty( $parsed_args['additional_errors'] ) ) {
3948              $message = array_merge(
3949                  array( $message ),
3950                  wp_list_pluck( $parsed_args['additional_errors'], 'message' )
3951              );
3952              $message = "<ul>\n\t\t<li>" . implode( "</li>\n\t\t<li>", $message ) . "</li>\n\t</ul>";
3953          }
3954  
3955          $message = sprintf(
3956              '<div class="wp-die-message">%s</div>',
3957              $message
3958          );
3959      }
3960  
3961      $have_gettext = function_exists( '__' );
3962  
3963      if ( ! empty( $parsed_args['link_url'] ) && ! empty( $parsed_args['link_text'] ) ) {
3964          $link_url = $parsed_args['link_url'];
3965          if ( function_exists( 'esc_url' ) ) {
3966              $link_url = esc_url( $link_url );
3967          }
3968          $link_text = $parsed_args['link_text'];
3969          $message  .= "\n<p><a href='{$link_url}'>{$link_text}</a></p>";
3970      }
3971  
3972      if ( isset( $parsed_args['back_link'] ) && $parsed_args['back_link'] ) {
3973          $back_text = $have_gettext ? __( '&laquo; Back' ) : '&laquo; Back';
3974          $message  .= "\n<p><a href='javascript:history.back()'>$back_text</a></p>";
3975      }
3976  
3977      if ( ! did_action( 'admin_head' ) ) :
3978          if ( ! headers_sent() ) {
3979              header( "Content-Type: text/html; charset={$parsed_args['charset']}" );
3980              status_header( $parsed_args['response'] );
3981              nocache_headers();
3982          }
3983  
3984          $text_direction = $parsed_args['text_direction'];
3985          $dir_attr       = "dir='$text_direction'";
3986  
3987          /*
3988           * If `text_direction` was not explicitly passed,
3989           * use get_language_attributes() if available.
3990           */
3991          if ( empty( $args['text_direction'] )
3992              && function_exists( 'language_attributes' ) && function_exists( 'is_rtl' )
3993          ) {
3994              $dir_attr = get_language_attributes();
3995          }
3996          ?>
3997  <!DOCTYPE html>
3998  <html <?php echo $dir_attr; ?>>
3999  <head>
4000      <meta http-equiv="Content-Type" content="text/html; charset=<?php echo $parsed_args['charset']; ?>" />
4001      <meta name="viewport" content="width=device-width, initial-scale=1.0">
4002          <?php
4003          if ( function_exists( 'wp_robots' ) && function_exists( 'wp_robots_no_robots' ) && function_exists( 'add_filter' ) ) {
4004              add_filter( 'wp_robots', 'wp_robots_no_robots' );
4005              // Prevent warnings because of $wp_query not existing.
4006              remove_filter( 'wp_robots', 'wp_robots_noindex_embeds' );
4007              remove_filter( 'wp_robots', 'wp_robots_noindex_search' );
4008              wp_robots();
4009          }
4010          ?>
4011      <title><?php echo $title; ?></title>
4012      <style>
4013          html {
4014              background: #f1f1f1;
4015          }
4016          body {
4017              background: #fff;
4018              border: 1px solid #ccd0d4;
4019              color: #444;
4020              font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
4021              margin: 2em auto;
4022              padding: 1em 2em;
4023              max-width: 700px;
4024              -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
4025              box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
4026          }
4027          h1 {
4028              border-bottom: 1px solid #dadada;
4029              clear: both;
4030              color: #666;
4031              font-size: 24px;
4032              margin: 30px 0 0 0;
4033              padding: 0;
4034              padding-bottom: 7px;
4035          }
4036          #error-page {
4037              margin-top: 50px;
4038          }
4039          #error-page p,
4040          #error-page .wp-die-message {
4041              font-size: 14px;
4042              line-height: 1.5;
4043              margin: 25px 0 20px;
4044          }
4045          #error-page code {
4046              font-family: Consolas, Monaco, monospace;
4047          }
4048          ul li {
4049              margin-bottom: 10px;
4050              font-size: 14px ;
4051          }
4052          a {
4053              color: #3858e9;
4054          }
4055          a:hover,
4056          a:active {
4057              color: #183ad6;
4058          }
4059          a:focus {
4060              color: #183ad6;
4061              box-shadow: 0 0 0 var(--wp-admin-border-width-focus, 1.5px) var(--wp-admin-theme-color, #3858e9);
4062              outline: 2px solid transparent;
4063          }
4064          .button {
4065              background: #f3f5f6;
4066              border: 1px solid #016087;
4067              color: #016087;
4068              display: inline-block;
4069              text-decoration: none;
4070              font-size: 13px;
4071              line-height: 2;
4072              height: 28px;
4073              margin: 0;
4074              padding: 0 10px 1px;
4075              cursor: pointer;
4076              -webkit-border-radius: 3px;
4077              -webkit-appearance: none;
4078              border-radius: 3px;
4079              white-space: nowrap;
4080              -webkit-box-sizing: border-box;
4081              -moz-box-sizing:    border-box;
4082              box-sizing:         border-box;
4083  
4084              vertical-align: top;
4085          }
4086  
4087          .button.button-large {
4088              line-height: 2.30769231;
4089              min-height: 32px;
4090              padding: 0 12px;
4091          }
4092  
4093          .button:hover,
4094          .button:focus {
4095              background: #f1f1f1;
4096          }
4097  
4098          .button:focus {
4099              background: #f3f5f6;
4100              border-color: #007cba;
4101              -webkit-box-shadow: 0 0 0 1px #007cba;
4102              box-shadow: 0 0 0 1px #007cba;
4103              color: #016087;
4104              outline: 2px solid transparent;
4105              outline-offset: 0;
4106          }
4107  
4108          .button:active {
4109              background: #f3f5f6;
4110              border-color: #7e8993;
4111              -webkit-box-shadow: none;
4112              box-shadow: none;
4113          }
4114  
4115          <?php
4116          if ( 'rtl' === $text_direction ) {
4117              echo 'body { font-family: Tahoma, Arial; }';
4118          }
4119          ?>
4120      </style>
4121  </head>
4122  <body id="error-page">
4123  <?php endif; // ! did_action( 'admin_head' ) ?>
4124      <?php echo $message; ?>
4125  </body>
4126  </html>
4127      <?php
4128      if ( $parsed_args['exit'] ) {
4129          die();
4130      }
4131  }
4132  
4133  /**
4134   * Kills WordPress execution and displays Ajax response with an error message.
4135   *
4136   * This is the handler for wp_die() when processing Ajax requests.
4137   *
4138   * @since 3.4.0
4139   * @access private
4140   *
4141   * @param string       $message Error message.
4142   * @param string       $title   Optional. Error title (unused). Default empty string.
4143   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
4144   * @phpstan-return ( $args is array{exit: false} ? void : never )
4145   */
4146  function _ajax_wp_die_handler( $message, $title = '', $args = array() ) {
4147      // Set default 'response' to 200 for Ajax requests.
4148      $args = wp_parse_args(
4149          $args,
4150          array( 'response' => 200 )
4151      );
4152  
4153      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
4154  
4155      if ( ! headers_sent() ) {
4156          // This is intentional. For backward-compatibility, support passing null here.
4157          if ( null !== $args['response'] ) {
4158              status_header( $parsed_args['response'] );
4159          }
4160          nocache_headers();
4161      }
4162  
4163      if ( is_scalar( $message ) ) {
4164          $message = (string) $message;
4165      } else {
4166          $message = '0';
4167      }
4168  
4169      if ( $parsed_args['exit'] ) {
4170          die( $message );
4171      }
4172  
4173      echo $message;
4174  }
4175  
4176  /**
4177   * Kills WordPress execution and displays JSON response with an error message.
4178   *
4179   * This is the handler for wp_die() when processing JSON requests.
4180   *
4181   * @since 5.1.0
4182   * @access private
4183   *
4184   * @param string       $message Error message.
4185   * @param string       $title   Optional. Error title. Default empty string.
4186   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
4187   * @phpstan-return ( $args is array{exit: false} ? void : never )
4188   */
4189  function _json_wp_die_handler( $message, $title = '', $args = array() ) {
4190      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
4191  
4192      $data = array(
4193          'code'              => $parsed_args['code'],
4194          'message'           => $message,
4195          'data'              => array(
4196              'status' => $parsed_args['response'],
4197          ),
4198          'additional_errors' => $parsed_args['additional_errors'],
4199      );
4200  
4201      if ( isset( $parsed_args['error_data'] ) ) {
4202          $data['data']['error'] = $parsed_args['error_data'];
4203      }
4204  
4205      if ( ! headers_sent() ) {
4206          header( "Content-Type: application/json; charset={$parsed_args['charset']}" );
4207          if ( null !== $parsed_args['response'] ) {
4208              status_header( $parsed_args['response'] );
4209          }
4210          nocache_headers();
4211      }
4212  
4213      echo wp_json_encode( $data );
4214      if ( $parsed_args['exit'] ) {
4215          die();
4216      }
4217  }
4218  
4219  /**
4220   * Kills WordPress execution and displays JSONP response with an error message.
4221   *
4222   * This is the handler for wp_die() when processing JSONP requests.
4223   *
4224   * @since 5.2.0
4225   * @access private
4226   *
4227   * @param string       $message Error message.
4228   * @param string       $title   Optional. Error title. Default empty string.
4229   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
4230   * @phpstan-return ( $args is array{exit: false} ? void : never )
4231   */
4232  function _jsonp_wp_die_handler( $message, $title = '', $args = array() ) {
4233      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
4234  
4235      $data = array(
4236          'code'              => $parsed_args['code'],
4237          'message'           => $message,
4238          'data'              => array(
4239              'status' => $parsed_args['response'],
4240          ),
4241          'additional_errors' => $parsed_args['additional_errors'],
4242      );
4243  
4244      if ( isset( $parsed_args['error_data'] ) ) {
4245          $data['data']['error'] = $parsed_args['error_data'];
4246      }
4247  
4248      if ( ! headers_sent() ) {
4249          header( "Content-Type: application/javascript; charset={$parsed_args['charset']}" );
4250          header( 'X-Content-Type-Options: nosniff' );
4251          header( 'X-Robots-Tag: noindex' );
4252          if ( null !== $parsed_args['response'] ) {
4253              status_header( $parsed_args['response'] );
4254          }
4255          nocache_headers();
4256      }
4257  
4258      $result         = wp_json_encode( $data );
4259      $jsonp_callback = $_GET['_jsonp'];
4260      echo '/**/' . $jsonp_callback . '(' . $result . ')';
4261      if ( $parsed_args['exit'] ) {
4262          die();
4263      }
4264  }
4265  
4266  /**
4267   * Kills WordPress execution and displays XML response with an error message.
4268   *
4269   * This is the handler for wp_die() when processing XML-RPC requests.
4270   *
4271   * @since 3.2.0
4272   * @access private
4273   *
4274   * @global wp_xmlrpc_server $wp_xmlrpc_server
4275   *
4276   * @param string       $message Error message.
4277   * @param string       $title   Optional. Error title. Default empty string.
4278   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
4279   * @phpstan-return ( $args is array{exit: false} ? void : never )
4280   */
4281  function _xmlrpc_wp_die_handler( $message, $title = '', $args = array() ) {
4282      global $wp_xmlrpc_server;
4283  
4284      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
4285  
4286      if ( ! headers_sent() ) {
4287          nocache_headers();
4288      }
4289  
4290      if ( $wp_xmlrpc_server ) {
4291          $error = new IXR_Error( $parsed_args['response'], $message );
4292          $wp_xmlrpc_server->output( $error->getXml() );
4293      }
4294      if ( $parsed_args['exit'] ) {
4295          die();
4296      }
4297  }
4298  
4299  /**
4300   * Kills WordPress execution and displays XML response with an error message.
4301   *
4302   * This is the handler for wp_die() when processing XML requests.
4303   *
4304   * @since 5.2.0
4305   * @access private
4306   *
4307   * @param string       $message Error message.
4308   * @param string       $title   Optional. Error title. Default empty string.
4309   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
4310   * @phpstan-return ( $args is array{exit: false} ? void : never )
4311   */
4312  function _xml_wp_die_handler( $message, $title = '', $args = array() ) {
4313      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
4314  
4315      $message = htmlspecialchars( $message );
4316      $title   = htmlspecialchars( $title );
4317  
4318      $xml = <<<EOD
4319  <error>
4320      <code>{$parsed_args['code']}</code>
4321      <title><![CDATA[{$title}]]></title>
4322      <message><![CDATA[{$message}]]></message>
4323      <data>
4324          <status>{$parsed_args['response']}</status>
4325      </data>
4326  </error>
4327  
4328  EOD;
4329  
4330      if ( ! headers_sent() ) {
4331          header( "Content-Type: text/xml; charset={$parsed_args['charset']}" );
4332          if ( null !== $parsed_args['response'] ) {
4333              status_header( $parsed_args['response'] );
4334          }
4335          nocache_headers();
4336      }
4337  
4338      echo $xml;
4339      if ( $parsed_args['exit'] ) {
4340          die();
4341      }
4342  }
4343  
4344  /**
4345   * Kills WordPress execution and displays an error message.
4346   *
4347   * This is the handler for wp_die() when processing APP requests.
4348   *
4349   * @since 3.4.0
4350   * @since 5.1.0 Added the $title and $args parameters.
4351   * @access private
4352   *
4353   * @param string       $message Optional. Response to print. Default empty string.
4354   * @param string       $title   Optional. Error title (unused). Default empty string.
4355   * @param string|array $args    Optional. Arguments to control behavior. Default empty array.
4356   * @phpstan-return ( $args is array{exit: false} ? void : never )
4357   */
4358  function _scalar_wp_die_handler( $message = '', $title = '', $args = array() ) {
4359      list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args );
4360  
4361      if ( $parsed_args['exit'] ) {
4362          if ( is_scalar( $message ) ) {
4363              die( (string) $message );
4364          }
4365          die();
4366      }
4367  
4368      if ( is_scalar( $message ) ) {
4369          echo (string) $message;
4370      }
4371  }
4372  
4373  /**
4374   * Processes arguments passed to wp_die() consistently for its handlers.
4375   *
4376   * @since 5.1.0
4377   * @access private
4378   *
4379   * @param string|WP_Error|int $message Error message, WP_Error object, or integer response.
4380   * @param string              $title   Optional. Error title. Default empty string.
4381   * @param string|array        $args    Optional. Arguments to control behavior. Default empty array.
4382   * @return array {
4383   *     Processed arguments.
4384   *
4385   *     @type string|int $0 Error message, or integer response.
4386   *     @type string     $1 Error title.
4387   *     @type array      $2 Arguments to control behavior.
4388   * }
4389   */
4390  function _wp_die_process_input( $message, $title = '', $args = array() ) {
4391      $defaults = array(
4392          'response'          => 0,
4393          'code'              => '',
4394          'exit'              => true,
4395          'back_link'         => false,
4396          'link_url'          => '',
4397          'link_text'         => '',
4398          'text_direction'    => '',
4399          'charset'           => 'utf-8',
4400          'additional_errors' => array(),
4401      );
4402  
4403      $args = wp_parse_args( $args, $defaults );
4404  
4405      if ( function_exists( 'is_wp_error' ) && is_wp_error( $message ) ) {
4406          if ( ! empty( $message->errors ) ) {
4407              $errors = array();
4408              foreach ( (array) $message->errors as $error_code => $error_messages ) {
4409                  foreach ( (array) $error_messages as $error_message ) {
4410                      $errors[] = array(
4411                          'code'    => $error_code,
4412                          'message' => $error_message,
4413                          'data'    => $message->get_error_data( $error_code ),
4414                      );
4415                  }
4416              }
4417  
4418              $message = $errors[0]['message'];
4419              if ( empty( $args['code'] ) ) {
4420                  $args['code'] = $errors[0]['code'];
4421              }
4422              if ( empty( $args['response'] ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['status'] ) ) {
4423                  $args['response'] = $errors[0]['data']['status'];
4424              }
4425              if ( empty( $title ) && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['title'] ) ) {
4426                  $title = $errors[0]['data']['title'];
4427              }
4428              if ( WP_DEBUG_DISPLAY && is_array( $errors[0]['data'] ) && ! empty( $errors[0]['data']['error'] ) ) {
4429                  $args['error_data'] = $errors[0]['data']['error'];
4430              }
4431  
4432              unset( $errors[0] );
4433              $args['additional_errors'] = array_values( $errors );
4434          } else {
4435              $message = '';
4436          }
4437      }
4438  
4439      $have_gettext = function_exists( '__' );
4440  
4441      // The $title and these specific $args must always have a non-empty value.
4442      if ( empty( $args['code'] ) ) {
4443          $args['code'] = 'wp_die';
4444      }
4445      if ( empty( $args['response'] ) ) {
4446          $args['response'] = 500;
4447      }
4448      if ( empty( $title ) ) {
4449          $title = $have_gettext ? __( 'WordPress &rsaquo; Error' ) : 'WordPress &rsaquo; Error';
4450      }
4451      if ( empty( $args['text_direction'] ) || ! in_array( $args['text_direction'], array( 'ltr', 'rtl' ), true ) ) {
4452          $args['text_direction'] = 'ltr';
4453          if ( function_exists( 'is_rtl' ) && is_rtl() ) {
4454              $args['text_direction'] = 'rtl';
4455          }
4456      }
4457  
4458      if ( ! empty( $args['charset'] ) ) {
4459          $args['charset'] = _canonical_charset( $args['charset'] );
4460      }
4461  
4462      return array( $message, $title, $args );
4463  }
4464  
4465  /**
4466   * Encodes a variable into JSON, with some confidence checks.
4467   *
4468   * @since 4.1.0
4469   * @since 5.3.0 No longer handles support for PHP < 5.6.
4470   * @since 6.5.0 The `$data` parameter has been renamed to `$value` and
4471   *              the `$options` parameter to `$flags` for parity with PHP.
4472   *
4473   * @param mixed $value Variable (usually an array or object) to encode as JSON.
4474   * @param int   $flags Optional. Options to be passed to json_encode(). Default 0.
4475   * @param int   $depth Optional. Maximum depth to walk through $value. Must be
4476   *                     greater than 0. Default 512.
4477   * @return string|false The JSON encoded string, or false if it cannot be encoded.
4478   */
4479  function wp_json_encode( $value, $flags = 0, $depth = 512 ) {
4480      $json = json_encode( $value, $flags, $depth );
4481  
4482      // If json_encode() was successful, no need to do more confidence checking.
4483      if ( false !== $json ) {
4484          return $json;
4485      }
4486  
4487      try {
4488          $value = _wp_json_sanity_check( $value, $depth );
4489      } catch ( Exception $e ) {
4490          return false;
4491      }
4492  
4493      return json_encode( $value, $flags, $depth );
4494  }
4495  
4496  /**
4497   * Performs confidence checks on data that shall be encoded to JSON.
4498   *
4499   * @ignore
4500   * @since 4.1.0
4501   * @access private
4502   *
4503   * @see wp_json_encode()
4504   *
4505   * @throws Exception If depth limit is reached.
4506   *
4507   * @param mixed $value Variable (usually an array or object) to encode as JSON.
4508   * @param int   $depth Maximum depth to walk through $value. Must be greater than 0.
4509   * @return mixed The sanitized data that shall be encoded to JSON.
4510   */
4511  function _wp_json_sanity_check( $value, $depth ) {
4512      if ( $depth < 0 ) {
4513          throw new Exception( 'Reached depth limit' );
4514      }
4515  
4516      if ( is_array( $value ) ) {
4517          $output = array();
4518          foreach ( $value as $id => $el ) {
4519              // Don't forget to sanitize the ID!
4520              if ( is_string( $id ) ) {
4521                  $clean_id = _wp_json_convert_string( $id );
4522              } else {
4523                  $clean_id = $id;
4524              }
4525  
4526              // Check the element type, so that we're only recursing if we really have to.
4527              if ( is_array( $el ) || is_object( $el ) ) {
4528                  $output[ $clean_id ] = _wp_json_sanity_check( $el, $depth - 1 );
4529              } elseif ( is_string( $el ) ) {
4530                  $output[ $clean_id ] = _wp_json_convert_string( $el );
4531              } else {
4532                  $output[ $clean_id ] = $el;
4533              }
4534          }
4535      } elseif ( is_object( $value ) ) {
4536          $output = new stdClass();
4537          foreach ( $value as $id => $el ) {
4538              if ( is_string( $id ) ) {
4539                  $clean_id = _wp_json_convert_string( $id );
4540              } else {
4541                  $clean_id = $id;
4542              }
4543  
4544              if ( is_array( $el ) || is_object( $el ) ) {
4545                  $output->$clean_id = _wp_json_sanity_check( $el, $depth - 1 );
4546              } elseif ( is_string( $el ) ) {
4547                  $output->$clean_id = _wp_json_convert_string( $el );
4548              } else {
4549                  $output->$clean_id = $el;
4550              }
4551          }
4552      } elseif ( is_string( $value ) ) {
4553          return _wp_json_convert_string( $value );
4554      } else {
4555          return $value;
4556      }
4557  
4558      return $output;
4559  }
4560  
4561  /**
4562   * Converts a string to UTF-8, so that it can be safely encoded to JSON.
4563   *
4564   * @ignore
4565   * @since 4.1.0
4566   * @access private
4567   *
4568   * @see _wp_json_sanity_check()
4569   *
4570   * @param string $input_string The string which is to be converted.
4571   * @return string The checked string.
4572   */
4573  function _wp_json_convert_string( $input_string ) {
4574      static $use_mb = null;
4575      if ( is_null( $use_mb ) ) {
4576          $use_mb = function_exists( 'mb_convert_encoding' );
4577      }
4578  
4579      if ( $use_mb ) {
4580          $encoding = mb_detect_encoding( $input_string, mb_detect_order(), true );
4581          if ( $encoding ) {
4582              return mb_convert_encoding( $input_string, 'UTF-8', $encoding );
4583          } else {
4584              return mb_convert_encoding( $input_string, 'UTF-8', 'UTF-8' );
4585          }
4586      } else {
4587          return wp_check_invalid_utf8( $input_string, true );
4588      }
4589  }
4590  
4591  /**
4592   * Prepares response data to be serialized to JSON.
4593   *
4594   * This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
4595   *
4596   * @ignore
4597   * @since 4.4.0
4598   * @deprecated 5.3.0 This function is no longer needed as support for PHP 5.2-5.3
4599   *                   has been dropped.
4600   * @access private
4601   *
4602   * @param mixed $value Native representation.
4603   * @return bool|int|float|null|string|array Data ready for `json_encode()`.
4604   */
4605  function _wp_json_prepare_data( $value ) {
4606      _deprecated_function( __FUNCTION__, '5.3.0' );
4607      return $value;
4608  }
4609  
4610  /**
4611   * Sends a JSON response back to an Ajax request.
4612   *
4613   * @since 3.5.0
4614   * @since 4.7.0 The `$status_code` parameter was added.
4615   * @since 5.6.0 The `$flags` parameter was added.
4616   *
4617   * @param mixed $response    Variable (usually an array or object) to encode as JSON,
4618   *                           then print and die.
4619   * @param int   $status_code Optional. The HTTP status code to output. Default null.
4620   * @param int   $flags       Optional. Options to be passed to json_encode(). Default 0.
4621   * @return never
4622   */
4623  function wp_send_json( $response, $status_code = null, $flags = 0 ) {
4624      if ( wp_is_serving_rest_request() ) {
4625          _doing_it_wrong(
4626              __FUNCTION__,
4627              sprintf(
4628                  /* translators: 1: WP_REST_Response, 2: WP_Error */
4629                  __( 'Return a %1$s or %2$s object from your callback when using the REST API.' ),
4630                  'WP_REST_Response',
4631                  'WP_Error'
4632              ),
4633              '5.5.0'
4634          );
4635      }
4636  
4637      if ( ! headers_sent() ) {
4638          header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
4639          if ( null !== $status_code ) {
4640              status_header( $status_code );
4641          }
4642      }
4643  
4644      echo wp_json_encode( $response, $flags );
4645  
4646      if ( wp_doing_ajax() ) {
4647          wp_die(
4648              '',
4649              '',
4650              array(
4651                  'response' => null,
4652              )
4653          );
4654      } else {
4655          die;
4656      }
4657  }
4658  
4659  /**
4660   * Sends a JSON response back to an Ajax request, indicating success.
4661   *
4662   * @since 3.5.0
4663   * @since 4.7.0 The `$status_code` parameter was added.
4664   * @since 5.6.0 The `$flags` parameter was added.
4665   *
4666   * @param mixed $value       Optional. Data to encode as JSON, then print and die. Default null.
4667   * @param int   $status_code Optional. The HTTP status code to output. Default null.
4668   * @param int   $flags       Optional. Options to be passed to json_encode(). Default 0.
4669   * @return never
4670   */
4671  function wp_send_json_success( $value = null, $status_code = null, $flags = 0 ) {
4672      $response = array( 'success' => true );
4673  
4674      if ( isset( $value ) ) {
4675          $response['data'] = $value;
4676      }
4677  
4678      wp_send_json( $response, $status_code, $flags );
4679  }
4680  
4681  /**
4682   * Sends a JSON response back to an Ajax request, indicating failure.
4683   *
4684   * If the `$value` parameter is a WP_Error object, the errors
4685   * within the object are processed and output as an array of error
4686   * codes and corresponding messages. All other types are output
4687   * without further processing.
4688   *
4689   * @since 3.5.0
4690   * @since 4.1.0 The `$value` parameter is now processed if a WP_Error object is passed in.
4691   * @since 4.7.0 The `$status_code` parameter was added.
4692   * @since 5.6.0 The `$flags` parameter was added.
4693   *
4694   * @param mixed $value       Optional. Data to encode as JSON, then print and die. Default null.
4695   * @param int   $status_code Optional. The HTTP status code to output. Default null.
4696   * @param int   $flags       Optional. Options to be passed to json_encode(). Default 0.
4697   * @return never
4698   */
4699  function wp_send_json_error( $value = null, $status_code = null, $flags = 0 ) {
4700      $response = array( 'success' => false );
4701  
4702      if ( isset( $value ) ) {
4703          if ( is_wp_error( $value ) ) {
4704              $result = array();
4705              foreach ( $value->errors as $code => $messages ) {
4706                  foreach ( $messages as $message ) {
4707                      $result[] = array(
4708                          'code'    => $code,
4709                          'message' => $message,
4710                      );
4711                  }
4712              }
4713  
4714              $response['data'] = $result;
4715          } else {
4716              $response['data'] = $value;
4717          }
4718      }
4719  
4720      wp_send_json( $response, $status_code, $flags );
4721  }
4722  
4723  /**
4724   * Checks that a JSONP callback is a valid JavaScript callback name.
4725   *
4726   * Only allows alphanumeric characters and the dot character in callback
4727   * function names. This helps to mitigate XSS attacks caused by directly
4728   * outputting user input.
4729   *
4730   * @since 4.6.0
4731   *
4732   * @param string $callback Supplied JSONP callback function name.
4733   * @return bool Whether the callback function name is valid.
4734   */
4735  function wp_check_jsonp_callback( $callback ) {
4736      if ( ! is_string( $callback ) ) {
4737          return false;
4738      }
4739  
4740      preg_replace( '/[^\w\.]/', '', $callback, -1, $illegal_char_count );
4741  
4742      return 0 === $illegal_char_count;
4743  }
4744  
4745  /**
4746   * Reads and decodes a JSON file.
4747   *
4748   * @since 5.9.0
4749   *
4750   * @param string $filename Path to the JSON file.
4751   * @param array  $options  {
4752   *     Optional. Options to be used with `json_decode()`.
4753   *
4754   *     @type bool $associative Optional. When `true`, JSON objects will be returned as associative arrays.
4755   *                             When `false`, JSON objects will be returned as objects. Default false.
4756   * }
4757   *
4758   * @return mixed Returns the value encoded in JSON in appropriate PHP type.
4759   *               `null` is returned if the file is not found, or its content can't be decoded.
4760   */
4761  function wp_json_file_decode( $filename, $options = array() ) {
4762      $result   = null;
4763      $filename = wp_normalize_path( realpath( $filename ) );
4764  
4765      if ( ! $filename ) {
4766          wp_trigger_error(
4767              __FUNCTION__,
4768              sprintf(
4769                  /* translators: %s: Path to the JSON file. */
4770                  __( "File %s doesn't exist!" ),
4771                  $filename
4772              )
4773          );
4774          return $result;
4775      }
4776  
4777      $options      = wp_parse_args( $options, array( 'associative' => false ) );
4778      $decoded_file = json_decode( file_get_contents( $filename ), $options['associative'] );
4779  
4780      if ( JSON_ERROR_NONE !== json_last_error() ) {
4781          wp_trigger_error(
4782              __FUNCTION__,
4783              sprintf(
4784                  /* translators: 1: Path to the JSON file, 2: Error message. */
4785                  __( 'Error when decoding a JSON file at path %1$s: %2$s' ),
4786                  $filename,
4787                  json_last_error_msg()
4788              )
4789          );
4790          return $result;
4791      }
4792  
4793      return $decoded_file;
4794  }
4795  
4796  /**
4797   * Retrieves the WordPress home page URL.
4798   *
4799   * If the constant named 'WP_HOME' exists, then it will be used and returned
4800   * by the function. This can be used to counter the redirection on your local
4801   * development environment.
4802   *
4803   * @since 2.2.0
4804   * @access private
4805   *
4806   * @see WP_HOME
4807   *
4808   * @param string $url URL for the home location.
4809   * @return string Homepage location.
4810   */
4811  function _config_wp_home( $url = '' ) {
4812      if ( defined( 'WP_HOME' ) ) {
4813          return untrailingslashit( WP_HOME );
4814      }
4815      return $url;
4816  }
4817  
4818  /**
4819   * Retrieves the WordPress site URL.
4820   *
4821   * If the constant named 'WP_SITEURL' is defined, then the value in that
4822   * constant will always be returned. This can be used for debugging a site
4823   * on your localhost while not having to change the database to your URL.
4824   *
4825   * @since 2.2.0
4826   * @access private
4827   *
4828   * @see WP_SITEURL
4829   *
4830   * @param string $url URL to set the WordPress site location.
4831   * @return string The WordPress site URL.
4832   */
4833  function _config_wp_siteurl( $url = '' ) {
4834      if ( defined( 'WP_SITEURL' ) ) {
4835          return untrailingslashit( WP_SITEURL );
4836      }
4837      return $url;
4838  }
4839  
4840  /**
4841   * Deletes the fresh site option.
4842   *
4843   * @since 4.7.0
4844   * @access private
4845   */
4846  function _delete_option_fresh_site() {
4847      update_option( 'fresh_site', '0', false );
4848  }
4849  
4850  /**
4851   * Sets the localized direction for MCE plugin.
4852   *
4853   * Will only set the direction to 'rtl', if the WordPress locale has
4854   * the text direction set to 'rtl'.
4855   *
4856   * Fills in the 'directionality' setting, enables the 'directionality'
4857   * plugin, and adds the 'ltr' button to 'toolbar1', formerly
4858   * 'theme_advanced_buttons1' array keys. These keys are then returned
4859   * in the $mce_init (TinyMCE settings) array.
4860   *
4861   * @since 2.1.0
4862   * @access private
4863   *
4864   * @param array $mce_init MCE settings array.
4865   * @return array Direction set for 'rtl', if needed by locale.
4866   */
4867  function _mce_set_direction( $mce_init ) {
4868      if ( is_rtl() ) {
4869          $mce_init['directionality'] = 'rtl';
4870          $mce_init['rtl_ui']         = true;
4871  
4872          if ( ! empty( $mce_init['plugins'] ) && ! str_contains( $mce_init['plugins'], 'directionality' ) ) {
4873              $mce_init['plugins'] .= ',directionality';
4874          }
4875  
4876          if ( ! empty( $mce_init['toolbar1'] ) && ! preg_match( '/\bltr\b/', $mce_init['toolbar1'] ) ) {
4877              $mce_init['toolbar1'] .= ',ltr';
4878          }
4879      }
4880  
4881      return $mce_init;
4882  }
4883  
4884  /**
4885   * Determines whether WordPress is currently serving a REST API request.
4886   *
4887   * The function relies on the 'REST_REQUEST' global. As such, it only returns true when an actual REST _request_ is
4888   * being made. It does not return true when a REST endpoint is hit as part of another request, e.g. for preloading a
4889   * REST response. See {@see wp_is_rest_endpoint()} for that purpose.
4890   *
4891   * This function should not be called until the {@see 'parse_request'} action, as the constant is only defined then,
4892   * even for an actual REST request.
4893   *
4894   * @since 6.5.0
4895   *
4896   * @return bool True if it's a WordPress REST API request, false otherwise.
4897   */
4898  function wp_is_serving_rest_request() {
4899      return defined( 'REST_REQUEST' ) && REST_REQUEST;
4900  }
4901  
4902  /**
4903   * Converts smiley code to the icon graphic file equivalent.
4904   *
4905   * You can turn off smilies, by going to the write setting screen and unchecking
4906   * the box, or by setting 'use_smilies' option to false or removing the option.
4907   *
4908   * Plugins may override the default smiley list by setting the $wpsmiliestrans
4909   * to an array, with the key the code the blogger types in and the value the
4910   * image file.
4911   *
4912   * The $wp_smiliessearch global is for the regular expression and is set each
4913   * time the function is called.
4914   *
4915   * The full list of smilies can be found in the function and won't be listed in
4916   * the description. Probably should create a Codex page for it, so that it is
4917   * available.
4918   *
4919   * @since 2.2.0
4920   *
4921   * @global array $wpsmiliestrans
4922   * @global array $wp_smiliessearch
4923   */
4924  function smilies_init() {
4925      global $wpsmiliestrans, $wp_smiliessearch;
4926  
4927      // Don't bother setting up smilies if they are disabled.
4928      if ( ! get_option( 'use_smilies' ) ) {
4929          return;
4930      }
4931  
4932      if ( ! isset( $wpsmiliestrans ) ) {
4933          $wpsmiliestrans = array(
4934              ':mrgreen:' => 'mrgreen.png',
4935              ':neutral:' => "\xf0\x9f\x98\x90",
4936              ':twisted:' => "\xf0\x9f\x98\x88",
4937              ':arrow:'   => "\xe2\x9e\xa1",
4938              ':shock:'   => "\xf0\x9f\x98\xaf",
4939              ':smile:'   => "\xf0\x9f\x99\x82",
4940              ':???:'     => "\xf0\x9f\x98\x95",
4941              ':cool:'    => "\xf0\x9f\x98\x8e",
4942              ':evil:'    => "\xf0\x9f\x91\xbf",
4943              ':grin:'    => "\xf0\x9f\x98\x80",
4944              ':idea:'    => "\xf0\x9f\x92\xa1",
4945              ':oops:'    => "\xf0\x9f\x98\xb3",
4946              ':razz:'    => "\xf0\x9f\x98\x9b",
4947              ':roll:'    => "\xf0\x9f\x99\x84",
4948              ':wink:'    => "\xf0\x9f\x98\x89",
4949              ':cry:'     => "\xf0\x9f\x98\xa5",
4950              ':eek:'     => "\xf0\x9f\x98\xae",
4951              ':lol:'     => "\xf0\x9f\x98\x86",
4952              ':mad:'     => "\xf0\x9f\x98\xa1",
4953              ':sad:'     => "\xf0\x9f\x99\x81",
4954              '8-)'       => "\xf0\x9f\x98\x8e",
4955              '8-O'       => "\xf0\x9f\x98\xaf",
4956              ':-('       => "\xf0\x9f\x99\x81",
4957              ':-)'       => "\xf0\x9f\x99\x82",
4958              ':-?'       => "\xf0\x9f\x98\x95",
4959              ':-D'       => "\xf0\x9f\x98\x80",
4960              ':-P'       => "\xf0\x9f\x98\x9b",
4961              ':-o'       => "\xf0\x9f\x98\xae",
4962              ':-x'       => "\xf0\x9f\x98\xa1",
4963              ':-|'       => "\xf0\x9f\x98\x90",
4964              ';-)'       => "\xf0\x9f\x98\x89",
4965              // This one transformation breaks regular text with frequency.
4966              //     '8)' => "\xf0\x9f\x98\x8e",
4967              '8O'        => "\xf0\x9f\x98\xaf",
4968              ':('        => "\xf0\x9f\x99\x81",
4969              ':)'        => "\xf0\x9f\x99\x82",
4970              ':?'        => "\xf0\x9f\x98\x95",
4971              ':D'        => "\xf0\x9f\x98\x80",
4972              ':P'        => "\xf0\x9f\x98\x9b",
4973              ':o'        => "\xf0\x9f\x98\xae",
4974              ':x'        => "\xf0\x9f\x98\xa1",
4975              ':|'        => "\xf0\x9f\x98\x90",
4976              ';)'        => "\xf0\x9f\x98\x89",
4977              ':!:'       => "\xe2\x9d\x97",
4978              ':?:'       => "\xe2\x9d\x93",
4979          );
4980      }
4981  
4982      /**
4983       * Filters all the smilies.
4984       *
4985       * This filter must be added before `smilies_init` is run, as
4986       * it is normally only run once to setup the smilies regex.
4987       *
4988       * @since 4.7.0
4989       *
4990       * @param string[] $wpsmiliestrans List of the smilies' hexadecimal representations, keyed by their smily code.
4991       */
4992      $wpsmiliestrans = apply_filters( 'smilies', $wpsmiliestrans );
4993  
4994      if ( count( $wpsmiliestrans ) === 0 ) {
4995          return;
4996      }
4997  
4998      /*
4999       * NOTE: we sort the smilies in reverse key order. This is to make sure
5000       * we match the longest possible smilie (:???: vs :?) as the regular
5001       * expression used below is first-match
5002       */
5003      krsort( $wpsmiliestrans );
5004  
5005      $spaces = wp_spaces_regexp();
5006  
5007      // Begin first "subpattern".
5008      $wp_smiliessearch = '/(?<=' . $spaces . '|^)';
5009  
5010      $subchar = '';
5011      foreach ( (array) $wpsmiliestrans as $smiley => $img ) {
5012          $firstchar = substr( $smiley, 0, 1 );
5013          $rest      = substr( $smiley, 1 );
5014  
5015          // New subpattern?
5016          if ( $firstchar !== $subchar ) {
5017              if ( '' !== $subchar ) {
5018                  $wp_smiliessearch .= ')(?=' . $spaces . '|$)';  // End previous "subpattern".
5019                  $wp_smiliessearch .= '|(?<=' . $spaces . '|^)'; // Begin another "subpattern".
5020              }
5021  
5022              $subchar           = $firstchar;
5023              $wp_smiliessearch .= preg_quote( $firstchar, '/' ) . '(?:';
5024          } else {
5025              $wp_smiliessearch .= '|';
5026          }
5027  
5028          $wp_smiliessearch .= preg_quote( $rest, '/' );
5029      }
5030  
5031      $wp_smiliessearch .= ')(?=' . $spaces . '|$)/m';
5032  }
5033  
5034  /**
5035   * Merges user defined arguments into defaults array.
5036   *
5037   * This function is used throughout WordPress to allow for both string or array
5038   * to be merged into another array.
5039   *
5040   * The keys of the returned array are documented as strings, since that is what
5041   * callers mean by them, but they are not promised as such to static analysis.
5042   * Integer keys remain reachable through arguments that are perfectly valid:
5043   * `json_decode( '{"0":"a"}' )` is an object whose properties `get_object_vars()`
5044   * reports under an integer key, and `parse_str()` reads `0=a` as one as well.
5045   *
5046   * @since 2.2.0
5047   * @since 2.3.0 `$args` can now also be an object.
5048   *
5049   * @param string|array<string, mixed>|object $args     Value to merge with $defaults.
5050   * @param array<string, mixed>               $defaults Optional. Array that serves as the defaults.
5051   *                                                     Default empty array.
5052   * @return array<string, mixed> Merged user defined values with defaults.
5053   * @phpstan-return array<array-key, mixed>
5054   */
5055  function wp_parse_args( $args, $defaults = array() ): array {
5056      if ( is_object( $args ) ) {
5057          $parsed_args = get_object_vars( $args );
5058      } elseif ( is_array( $args ) ) {
5059          $parsed_args =& $args;
5060      } else {
5061          wp_parse_str( $args, $parsed_args );
5062      }
5063  
5064      if ( is_array( $defaults ) && $defaults ) {
5065          return array_merge( $defaults, $parsed_args );
5066      }
5067      return $parsed_args;
5068  }
5069  
5070  /**
5071   * Converts a comma- or space-separated list of scalar values to an array.
5072   *
5073   * @since 5.1.0
5074   * @since 7.2.0 Added explicit support for passing an integer.
5075   *
5076   * @param mixed[]|string|int $input_list List of values.
5077   * @return array Array of scalar values. A string is split into a list, while an array
5078   *               keeps its keys, so the result is not necessarily a list.
5079   * @phpstan-return (
5080   *     $input_list is string|int ? list<string> : (
5081   *         $input_list is array<string> ? array<string> : array<scalar>
5082   *     )
5083   * )
5084   */
5085  function wp_parse_list( $input_list ): array {
5086      if ( is_int( $input_list ) ) {
5087          $input_list = array( (string) $input_list );
5088      } elseif ( ! is_array( $input_list ) ) {
5089          $parsed_list = preg_split( '/[\s,]+/', $input_list, -1, PREG_SPLIT_NO_EMPTY );
5090          return is_array( $parsed_list ) ? $parsed_list : array();
5091      }
5092  
5093      // Validate all entries of the list are scalar.
5094      $input_list = array_filter( $input_list, 'is_scalar' );
5095  
5096      return $input_list;
5097  }
5098  
5099  /**
5100   * Cleans up an array, comma- or space-separated list of IDs.
5101   *
5102   * @since 3.0.0
5103   * @since 5.1.0 Refactored to use {@see wp_parse_list()}.
5104   * @since 7.2.0 Added explicit support for passing an integer.
5105   *
5106   * @param mixed[]|string|int $input_list List of IDs.
5107   * @return int[] Sanitized array of IDs. May include zero. Keys are preserved
5108   *               from the input and `array_unique()` may leave gaps, so the
5109   *               result is not necessarily a list.
5110   * @phpstan-return array<non-negative-int>
5111   */
5112  function wp_parse_id_list( $input_list ): array {
5113      $input_list = wp_parse_list( $input_list );
5114  
5115      return array_unique( array_map( 'absint', $input_list ) );
5116  }
5117  
5118  /**
5119   * Cleans up an array, comma- or space-separated list of slugs.
5120   *
5121   * @since 4.7.0
5122   * @since 5.1.0 Refactored to use {@see wp_parse_list()}.
5123   * @since 7.2.0 Added explicit support for passing an integer.
5124   *
5125   * @param mixed[]|string|int $input_list List of slugs.
5126   * @return string[] Sanitized array of slugs. May include an empty string. Keys
5127   *                  are preserved from the input and `array_unique()` may leave
5128   *                  gaps, so the result is not necessarily a list.
5129   */
5130  function wp_parse_slug_list( $input_list ): array {
5131      $input_list = wp_parse_list( $input_list );
5132  
5133      return array_unique(
5134          array_map(
5135              'sanitize_title',
5136              array_map(
5137                  /*
5138                   * Cast booleans, integers, and floats to strings. Non-scalar types
5139                   * (including null) have already been filtered out by wp_parse_list().
5140                   */
5141                  'strval',
5142                  $input_list
5143              )
5144          )
5145      );
5146  }
5147  
5148  /**
5149   * Extracts a slice of an array, given a list of keys.
5150   *
5151   * @since 3.1.0
5152   *
5153   * @param array<string, mixed> $input_array The original array.
5154   * @param string[]             $keys        The list of keys.
5155   * @return array<string, mixed> The array slice.
5156   *
5157   * @phpstan-template TKey of string
5158   * @phpstan-template TValue
5159   * @phpstan-param array<string, TValue> $input_array
5160   * @phpstan-param array<TKey> $keys
5161   * @phpstan-return array<TKey, TValue>
5162   */
5163  function wp_array_slice_assoc( $input_array, $keys ): array {
5164      $slice = array();
5165  
5166      foreach ( $keys as $key ) {
5167          if ( isset( $input_array[ $key ] ) ) {
5168              $slice[ $key ] = $input_array[ $key ];
5169          }
5170      }
5171  
5172      return $slice;
5173  }
5174  
5175  /**
5176   * Sorts the keys of an array alphabetically.
5177   *
5178   * The array is passed by reference so it doesn't get returned
5179   * which mimics the behavior of `ksort()`.
5180   *
5181   * @since 6.0.0
5182   *
5183   * @param array $input_array The array to sort, passed by reference.
5184   */
5185  function wp_recursive_ksort( &$input_array ) {
5186      foreach ( $input_array as &$value ) {
5187          if ( is_array( $value ) ) {
5188              wp_recursive_ksort( $value );
5189          }
5190      }
5191  
5192      ksort( $input_array );
5193  }
5194  
5195  /**
5196   * Accesses an array in depth based on a path of keys.
5197   *
5198   * It is the PHP equivalent of JavaScript's `lodash.get()` and mirroring it may help other components
5199   * retain some symmetry between client and server implementations.
5200   *
5201   * Example usage:
5202   *
5203   *     $input_array = array(
5204   *         'a' => array(
5205   *             'b' => array(
5206   *                 'c' => 1,
5207   *             ),
5208   *         ),
5209   *     );
5210   *     _wp_array_get( $input_array, array( 'a', 'b', 'c' ) );
5211   *
5212   * @internal
5213   *
5214   * @since 5.6.0
5215   * @access private
5216   *
5217   * @param array $input_array   An array from which we want to retrieve some information.
5218   * @param array $path          An array of keys describing the path with which to retrieve information.
5219   * @param mixed $default_value Optional. The return value if the path does not exist within the array,
5220   *                             or if `$input_array` or `$path` are not arrays. Default null.
5221   * @return mixed The value from the path specified.
5222   */
5223  function _wp_array_get( $input_array, $path, $default_value = null ) {
5224      // Confirm $path is valid.
5225      if ( ! is_array( $path ) || 0 === count( $path ) ) {
5226          return $default_value;
5227      }
5228  
5229      foreach ( $path as $path_element ) {
5230          if ( ! is_array( $input_array ) ) {
5231              return $default_value;
5232          }
5233  
5234          if ( is_string( $path_element )
5235              || is_int( $path_element )
5236              || null === $path_element
5237          ) {
5238              /*
5239               * Check if the path element exists in the input array.
5240               * We check with `isset()` first, as it is a lot faster
5241               * than `array_key_exists()`.
5242               */
5243              if ( isset( $path_element, $input_array[ $path_element ] ) ) {
5244                  $input_array = $input_array[ $path_element ];
5245                  continue;
5246              }
5247  
5248              /*
5249               * If `isset()` returns false, we check with `array_key_exists()`,
5250               * which also checks for `null` values.
5251               */
5252              if ( isset( $path_element ) && array_key_exists( $path_element, $input_array ) ) {
5253                  $input_array = $input_array[ $path_element ];
5254                  continue;
5255              }
5256          }
5257  
5258          return $default_value;
5259      }
5260  
5261      return $input_array;
5262  }
5263  
5264  /**
5265   * Sets an array in depth based on a path of keys.
5266   *
5267   * It is the PHP equivalent of JavaScript's `lodash.set()` and mirroring it may help other components
5268   * retain some symmetry between client and server implementations.
5269   *
5270   * Example usage:
5271   *
5272   *     $input_array = array();
5273   *     _wp_array_set( $input_array, array( 'a', 'b', 'c', 1 ) );
5274   *
5275   *     $input_array becomes:
5276   *     array(
5277   *         'a' => array(
5278   *             'b' => array(
5279   *                 'c' => 1,
5280   *             ),
5281   *         ),
5282   *     );
5283   *
5284   * @internal
5285   *
5286   * @since 5.8.0
5287   * @access private
5288   *
5289   * @param array $input_array An array that we want to mutate to include a specific value in a path.
5290   * @param array $path        An array of keys describing the path that we want to mutate.
5291   * @param mixed $value       The value that will be set.
5292   */
5293  function _wp_array_set( &$input_array, $path, $value = null ) {
5294      // Confirm $input_array is valid.
5295      if ( ! is_array( $input_array ) ) {
5296          return;
5297      }
5298  
5299      // Confirm $path is valid.
5300      if ( ! is_array( $path ) ) {
5301          return;
5302      }
5303  
5304      $path_length = count( $path );
5305  
5306      if ( 0 === $path_length ) {
5307          return;
5308      }
5309  
5310      foreach ( $path as $path_element ) {
5311          if (
5312              ! is_string( $path_element ) && ! is_int( $path_element ) &&
5313              ! is_null( $path_element )
5314          ) {
5315              return;
5316          }
5317      }
5318  
5319      for ( $i = 0; $i < $path_length - 1; ++$i ) {
5320          $path_element = $path[ $i ];
5321          if (
5322              ! array_key_exists( $path_element, $input_array ) ||
5323              ! is_array( $input_array[ $path_element ] )
5324          ) {
5325              $input_array[ $path_element ] = array();
5326          }
5327          $input_array = &$input_array[ $path_element ];
5328      }
5329  
5330      $input_array[ $path[ $i ] ] = $value;
5331  }
5332  
5333  /**
5334   * This function is trying to replicate what
5335   * lodash's kebabCase (JS library) does in the client.
5336   *
5337   * The reason we need this function is that we do some processing
5338   * in both the client and the server (e.g.: we generate
5339   * preset classes from preset slugs) that needs to
5340   * create the same output.
5341   *
5342   * We can't remove or update the client's library due to backward compatibility
5343   * (some of the output of lodash's kebabCase is saved in the post content).
5344   * We have to make the server behave like the client.
5345   *
5346   * Changes to this function should follow updates in the client
5347   * with the same logic.
5348   *
5349   * @since 5.8.0
5350   *
5351   * @link https://github.com/lodash/lodash/blob/4.17/dist/lodash.js#L14369
5352   * @link https://github.com/lodash/lodash/blob/4.17/dist/lodash.js#L278
5353   * @link https://github.com/lodash-php/lodash-php/blob/master/src/String/kebabCase.php
5354   * @link https://github.com/lodash-php/lodash-php/blob/master/src/internal/unicodeWords.php
5355   *
5356   * @param string $input_string The string to kebab-case.
5357   * @return string kebab-cased-string.
5358   */
5359  function _wp_to_kebab_case( $input_string ) {
5360      // Ignore the camelCase names for variables so the names are the same as lodash so comparing and porting new changes is easier.
5361      // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5362  
5363      /*
5364       * Some notable things we've removed compared to the lodash version are:
5365       *
5366       * - non-alphanumeric characters: rsAstralRange, rsEmoji, etc
5367       * - the groups that processed the apostrophe, as it's removed before passing the string to preg_match: rsApos, rsOptContrLower, and rsOptContrUpper
5368       *
5369       */
5370  
5371      /** Used to compose unicode character classes. */
5372      $rsLowerRange       = 'a-z\\xdf-\\xf6\\xf8-\\xff';
5373      $rsNonCharRange     = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf';
5374      $rsPunctuationRange = '\\x{2000}-\\x{206f}';
5375      $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}';
5376      $rsUpperRange       = 'A-Z\\xc0-\\xd6\\xd8-\\xde';
5377      $rsBreakRange       = $rsNonCharRange . $rsPunctuationRange . $rsSpaceRange;
5378  
5379      /** Used to compose unicode capture groups. */
5380      $rsBreak  = '[' . $rsBreakRange . ']';
5381      $rsDigits = '\\d+'; // The last lodash version in GitHub uses a single digit here and expands it when in use.
5382      $rsLower  = '[' . $rsLowerRange . ']';
5383      $rsMisc   = '[^' . $rsBreakRange . $rsDigits . $rsLowerRange . $rsUpperRange . ']';
5384      $rsUpper  = '[' . $rsUpperRange . ']';
5385  
5386      /** Used to compose unicode regexes. */
5387      $rsMiscLower = '(?:' . $rsLower . '|' . $rsMisc . ')';
5388      $rsMiscUpper = '(?:' . $rsUpper . '|' . $rsMisc . ')';
5389      $rsOrdLower  = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])';
5390      $rsOrdUpper  = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])';
5391  
5392      $regexp = '/' . implode(
5393          '|',
5394          array(
5395              $rsUpper . '?' . $rsLower . '+' . '(?=' . implode( '|', array( $rsBreak, $rsUpper, '$' ) ) . ')',
5396              $rsMiscUpper . '+' . '(?=' . implode( '|', array( $rsBreak, $rsUpper . $rsMiscLower, '$' ) ) . ')',
5397              $rsUpper . '?' . $rsMiscLower . '+',
5398              $rsUpper . '+',
5399              $rsOrdUpper,
5400              $rsOrdLower,
5401              $rsDigits,
5402          )
5403      ) . '/u';
5404  
5405      preg_match_all( $regexp, str_replace( "'", '', $input_string ), $matches );
5406      return strtolower( implode( '-', $matches[0] ) );
5407      // phpcs:enable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
5408  }
5409  
5410  /**
5411   * Determines if the variable is a numeric-indexed array.
5412   *
5413   * Note! This answers a different question than {@see array_is_list()} and is
5414   *       more flexible to handle situations where some numeric array indices
5415   *       have been removed. A numeric-indexed array is only a “list” when the
5416   *       array keys form a contiguous range from zero to the highest key.
5417   *
5418   * Example:
5419   *
5420   *     true  === wp_is_numeric_array( array( 1, 2, 3, 4 ) );
5421   *     false === wp_is_numeric_array( array( 'name' => 'WordPress' ) );
5422   *
5423   *     // All-numeric keys vs. list.
5424   *     $above_two   = array_filter( array( 1, 2, 8, 9 ), fn ( $v ) => $v > 2 );
5425   *     $above_two === array( '2' => 8, '3' => 9 );
5426   *     true       === wp_is_numeric_array( $above_two );
5427   *     false      === array_is_list( $above_two );
5428   *
5429   * @since 4.4.0
5430   *
5431   * @param mixed $data Variable to check.
5432   * @return bool Whether the variable is a list.
5433   *
5434   * @phpstan-assert-if-true array<int, mixed> $data
5435   *
5436   * @phpstan-return ( $data is array<int, mixed> ? true : false )
5437   */
5438  function wp_is_numeric_array( $data ): bool {
5439      if ( ! is_array( $data ) ) {
5440          return false;
5441      }
5442  
5443      return array_all( $data, fn( $value, $key ) => ! is_string( $key ) );
5444  }
5445  
5446  /**
5447   * Filters a list of objects, based on a set of key => value arguments.
5448   *
5449   * Retrieves the objects from the list that match the given arguments.
5450   * Key represents property name, and value represents property value.
5451   *
5452   * If an object has more properties than those specified in arguments,
5453   * that will not disqualify it. When using the 'AND' operator,
5454   * any missing properties will disqualify it.
5455   *
5456   * When using the `$field` argument, this function can also retrieve
5457   * a particular field from all matching objects, whereas wp_list_filter()
5458   * only does the filtering.
5459   *
5460   * @since 3.0.0
5461   * @since 4.7.0 Uses `WP_List_Util` class.
5462   *
5463   * @param array       $input_list An array of objects to filter.
5464   * @param array       $args       Optional. An array of key => value arguments to match
5465   *                                against each object. Default empty array.
5466   * @param string      $operator   Optional. The logical operation to perform. 'AND' means
5467   *                                all elements from the array must match. 'OR' means only
5468   *                                one element needs to match. 'NOT' means no elements may
5469   *                                match. Default 'AND'.
5470   * @param bool|string $field      Optional. A field from the object to place instead
5471   *                                of the entire object. Default false.
5472   * @return array A list of objects or object fields.
5473   */
5474  function wp_filter_object_list( $input_list, $args = array(), $operator = 'and', $field = false ) {
5475      if ( ! is_array( $input_list ) ) {
5476          return array();
5477      }
5478  
5479      $util = new WP_List_Util( $input_list );
5480  
5481      $util->filter( $args, $operator );
5482  
5483      if ( $field ) {
5484          $util->pluck( $field );
5485      }
5486  
5487      return $util->get_output();
5488  }
5489  
5490  /**
5491   * Filters a list of objects, based on a set of key => value arguments.
5492   *
5493   * Retrieves the objects from the list that match the given arguments.
5494   * Key represents property name, and value represents property value.
5495   *
5496   * If an object has more properties than those specified in arguments,
5497   * that will not disqualify it. When using the 'AND' operator,
5498   * any missing properties will disqualify it.
5499   *
5500   * If you want to retrieve a particular field from all matching objects,
5501   * use wp_filter_object_list() instead.
5502   *
5503   * @since 3.1.0
5504   * @since 4.7.0 Uses `WP_List_Util` class.
5505   * @since 5.9.0 Converted into a wrapper for `wp_filter_object_list()`.
5506   *
5507   * @param array  $input_list An array of objects to filter.
5508   * @param array  $args       Optional. An array of key => value arguments to match
5509   *                           against each object. Default empty array.
5510   * @param string $operator   Optional. The logical operation to perform. 'AND' means
5511   *                           all elements from the array must match. 'OR' means only
5512   *                           one element needs to match. 'NOT' means no elements may
5513   *                           match. Default 'AND'.
5514   * @return array Array of found values.
5515   */
5516  function wp_list_filter( $input_list, $args = array(), $operator = 'AND' ) {
5517      return wp_filter_object_list( $input_list, $args, $operator );
5518  }
5519  
5520  /**
5521   * Plucks a certain field out of each object or array in an array.
5522   *
5523   * This has the same functionality and prototype of
5524   * array_column() (PHP 5.5) but also supports objects.
5525   *
5526   * @since 3.1.0
5527   * @since 4.0.0 $index_key parameter added.
5528   * @since 4.7.0 Uses `WP_List_Util` class.
5529   *
5530   * @param array      $input_list List of objects or arrays.
5531   * @param int|string $field      Field from the object to place instead of the entire object.
5532   * @param int|string $index_key  Optional. Field from the object to use as keys for the new array.
5533   *                               Default null.
5534   * @return array Array of found values. If `$index_key` is set, an array of found values with keys
5535   *               corresponding to `$index_key`. If `$index_key` is null, array keys from the original
5536   *               `$input_list` will be preserved in the results.
5537   */
5538  function wp_list_pluck( $input_list, $field, $index_key = null ) {
5539      if ( ! is_array( $input_list ) ) {
5540          return array();
5541      }
5542  
5543      $util = new WP_List_Util( $input_list );
5544  
5545      return $util->pluck( $field, $index_key );
5546  }
5547  
5548  /**
5549   * Sorts an array of objects or arrays based on one or more orderby arguments.
5550   *
5551   * @since 4.7.0
5552   *
5553   * @param array        $input_list    An array of objects or arrays to sort.
5554   * @param string|array $orderby       Optional. Either the field name to order by or an array
5555   *                                    of multiple orderby fields as `$orderby => $order`.
5556   *                                    Default empty array.
5557   * @param string       $order         Optional. Either 'ASC' or 'DESC'. Only used if `$orderby`
5558   *                                    is a string. Default 'ASC'.
5559   * @param bool         $preserve_keys Optional. Whether to preserve keys. Default false.
5560   * @return array The sorted array.
5561   */
5562  function wp_list_sort( $input_list, $orderby = array(), $order = 'ASC', $preserve_keys = false ) {
5563      if ( ! is_array( $input_list ) ) {
5564          return array();
5565      }
5566  
5567      $util = new WP_List_Util( $input_list );
5568  
5569      return $util->sort( $orderby, $order, $preserve_keys );
5570  }
5571  
5572  /**
5573   * Determines if Widgets library should be loaded.
5574   *
5575   * Checks to make sure that the widgets library hasn't already been loaded.
5576   * If it hasn't, then it will load the widgets library and run an action hook.
5577   *
5578   * @since 2.2.0
5579   */
5580  function wp_maybe_load_widgets() {
5581      /**
5582       * Filters whether to load the Widgets library.
5583       *
5584       * Returning a falsey value from the filter will effectively short-circuit
5585       * the Widgets library from loading.
5586       *
5587       * @since 2.8.0
5588       *
5589       * @param bool $wp_maybe_load_widgets Whether to load the Widgets library.
5590       *                                    Default true.
5591       */
5592      if ( ! apply_filters( 'load_default_widgets', true ) ) {
5593          return;
5594      }
5595  
5596      require_once  ABSPATH . WPINC . '/default-widgets.php';
5597  
5598      add_action( '_admin_menu', 'wp_widgets_add_menu' );
5599  }
5600  
5601  /**
5602   * Appends the Widgets menu to the themes main menu.
5603   *
5604   * @since 2.2.0
5605   * @since 5.9.3 Don't specify menu order when the active theme is a block theme.
5606   *
5607   * @global array $submenu
5608   */
5609  function wp_widgets_add_menu() {
5610      global $submenu;
5611  
5612      if ( ! current_theme_supports( 'widgets' ) ) {
5613          return;
5614      }
5615  
5616      $menu_name = __( 'Widgets' );
5617      if ( wp_is_block_theme() ) {
5618          $submenu['themes.php'][] = array( $menu_name, 'edit_theme_options', 'widgets.php' );
5619      } else {
5620          $submenu['themes.php'][8] = array( $menu_name, 'edit_theme_options', 'widgets.php' );
5621      }
5622  
5623      ksort( $submenu['themes.php'], SORT_NUMERIC );
5624  }
5625  
5626  /**
5627   * Flushes all output buffers for PHP 5.2.
5628   *
5629   * Make sure all output buffers are flushed before our singletons are destroyed.
5630   *
5631   * @since 2.2.0
5632   */
5633  function wp_ob_end_flush_all() {
5634      $levels = ob_get_level();
5635      for ( $i = 0; $i < $levels; $i++ ) {
5636          ob_end_flush();
5637      }
5638  }
5639  
5640  /**
5641   * Loads custom DB error or display WordPress DB error.
5642   *
5643   * If a file exists in the wp-content directory named db-error.php, then it will
5644   * be loaded instead of displaying the WordPress DB error. If it is not found,
5645   * then the WordPress DB error will be displayed instead.
5646   *
5647   * The WordPress DB error sets the HTTP status header to 500 to try to prevent
5648   * search engines from caching the message. Custom DB messages should do the
5649   * same.
5650   *
5651   * This function was backported to WordPress 2.3.2, but originally was added
5652   * in WordPress 2.5.0.
5653   *
5654   * @since 2.3.2
5655   *
5656   * @global wpdb $wpdb WordPress database abstraction object.
5657   *
5658   * @return never
5659   */
5660  function dead_db() {
5661      global $wpdb;
5662  
5663      wp_load_translations_early();
5664  
5665      // Load custom DB error template, if present.
5666      if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
5667          require_once WP_CONTENT_DIR . '/db-error.php';
5668          die();
5669      }
5670  
5671      // If installing or in the admin, provide the verbose message.
5672      if ( wp_installing() || defined( 'WP_ADMIN' ) ) {
5673          wp_die( $wpdb->error );
5674      }
5675  
5676      // Otherwise, be terse.
5677      wp_die( '<h1>' . __( 'Error establishing a database connection' ) . '</h1>', __( 'Database Error' ) );
5678  }
5679  
5680  /**
5681   * Marks a function as deprecated and inform when it has been used.
5682   *
5683   * There is a {@see 'deprecated_function_run'} hook that will be called that can be used
5684   * to get the backtrace up to what file and function called the deprecated function.
5685   *
5686   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
5687   *
5688   * This function is to be used in every function that is deprecated.
5689   *
5690   * @since 2.5.0
5691   * @since 5.4.0 This function is no longer marked as "private".
5692   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
5693   *
5694   * @param string $function_name The function that was called.
5695   * @param string $version       The version of WordPress that deprecated the function.
5696   * @param string $replacement   Optional. The function that should have been called. Default empty string.
5697   */
5698  function _deprecated_function( $function_name, $version, $replacement = '' ) {
5699  
5700      /**
5701       * Fires when a deprecated function is called.
5702       *
5703       * @since 2.5.0
5704       *
5705       * @param string $function_name The function that was called.
5706       * @param string $replacement   The function that should have been called.
5707       * @param string $version       The version of WordPress that deprecated the function.
5708       */
5709      do_action( 'deprecated_function_run', $function_name, $replacement, $version );
5710  
5711      /**
5712       * Filters whether to trigger an error for deprecated functions.
5713       *
5714       * @since 2.5.0
5715       *
5716       * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
5717       */
5718      if ( WP_DEBUG && apply_filters( 'deprecated_function_trigger_error', true ) ) {
5719          if ( function_exists( '__' ) ) {
5720              if ( $replacement ) {
5721                  $message = sprintf(
5722                      /* translators: 1: PHP function name, 2: Version number, 3: Alternative function name. */
5723                      __( 'Function %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
5724                      $function_name,
5725                      $version,
5726                      $replacement
5727                  );
5728              } else {
5729                  $message = sprintf(
5730                      /* translators: 1: PHP function name, 2: Version number. */
5731                      __( 'Function %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
5732                      $function_name,
5733                      $version
5734                  );
5735              }
5736          } else {
5737              if ( $replacement ) {
5738                  $message = sprintf(
5739                      'Function %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
5740                      $function_name,
5741                      $version,
5742                      $replacement
5743                  );
5744              } else {
5745                  $message = sprintf(
5746                      'Function %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
5747                      $function_name,
5748                      $version
5749                  );
5750              }
5751          }
5752  
5753          wp_trigger_error( '', $message, E_USER_DEPRECATED );
5754      }
5755  }
5756  
5757  /**
5758   * Marks a constructor as deprecated and informs when it has been used.
5759   *
5760   * Similar to _deprecated_function(), but with different strings. Used to
5761   * remove PHP4-style constructors.
5762   *
5763   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
5764   *
5765   * This function is to be used in every PHP4-style constructor method that is deprecated.
5766   *
5767   * @since 4.3.0
5768   * @since 4.5.0 Added the `$parent_class` parameter.
5769   * @since 5.4.0 This function is no longer marked as "private".
5770   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
5771   *
5772   * @param string $class_name   The class containing the deprecated constructor.
5773   * @param string $version      The version of WordPress that deprecated the function.
5774   * @param string $parent_class Optional. The parent class calling the deprecated constructor.
5775   *                             Default empty string.
5776   */
5777  function _deprecated_constructor( $class_name, $version, $parent_class = '' ) {
5778  
5779      /**
5780       * Fires when a deprecated constructor is called.
5781       *
5782       * @since 4.3.0
5783       * @since 4.5.0 Added the `$parent_class` parameter.
5784       *
5785       * @param string $class_name   The class containing the deprecated constructor.
5786       * @param string $version      The version of WordPress that deprecated the function.
5787       * @param string $parent_class The parent class calling the deprecated constructor.
5788       */
5789      do_action( 'deprecated_constructor_run', $class_name, $version, $parent_class );
5790  
5791      /**
5792       * Filters whether to trigger an error for deprecated functions.
5793       *
5794       * `WP_DEBUG` must be true in addition to the filter evaluating to true.
5795       *
5796       * @since 4.3.0
5797       *
5798       * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
5799       */
5800      if ( WP_DEBUG && apply_filters( 'deprecated_constructor_trigger_error', true ) ) {
5801          if ( function_exists( '__' ) ) {
5802              if ( $parent_class ) {
5803                  $message = sprintf(
5804                      /* translators: 1: PHP class name, 2: PHP parent class name, 3: Version number, 4: __construct() method. */
5805                      __( 'The called constructor method for %1$s class in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.' ),
5806                      $class_name,
5807                      $parent_class,
5808                      $version,
5809                      '<code>__construct()</code>'
5810                  );
5811              } else {
5812                  $message = sprintf(
5813                      /* translators: 1: PHP class name, 2: Version number, 3: __construct() method. */
5814                      __( 'The called constructor method for %1$s class is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
5815                      $class_name,
5816                      $version,
5817                      '<code>__construct()</code>'
5818                  );
5819              }
5820          } else {
5821              if ( $parent_class ) {
5822                  $message = sprintf(
5823                      'The called constructor method for %1$s class in %2$s is <strong>deprecated</strong> since version %3$s! Use %4$s instead.',
5824                      $class_name,
5825                      $parent_class,
5826                      $version,
5827                      '<code>__construct()</code>'
5828                  );
5829              } else {
5830                  $message = sprintf(
5831                      'The called constructor method for %1$s class is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
5832                      $class_name,
5833                      $version,
5834                      '<code>__construct()</code>'
5835                  );
5836              }
5837          }
5838  
5839          wp_trigger_error( '', $message, E_USER_DEPRECATED );
5840      }
5841  }
5842  
5843  /**
5844   * Marks a class as deprecated and informs when it has been used.
5845   *
5846   * There is a {@see 'deprecated_class_run'} hook that will be called that can be used
5847   * to get the backtrace up to what file and function called the deprecated class.
5848   *
5849   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
5850   *
5851   * This function is to be used in the class constructor for every deprecated class.
5852   * See {@see _deprecated_constructor()} for deprecating PHP4-style constructors.
5853   *
5854   * @since 6.4.0
5855   *
5856   * @param string $class_name  The name of the class being instantiated.
5857   * @param string $version     The version of WordPress that deprecated the class.
5858   * @param string $replacement Optional. The class or function that should have been called.
5859   *                            Default empty string.
5860   */
5861  function _deprecated_class( $class_name, $version, $replacement = '' ) {
5862  
5863      /**
5864       * Fires when a deprecated class is called.
5865       *
5866       * @since 6.4.0
5867       *
5868       * @param string $class_name  The name of the class being instantiated.
5869       * @param string $replacement The class or function that should have been called.
5870       * @param string $version     The version of WordPress that deprecated the class.
5871       */
5872      do_action( 'deprecated_class_run', $class_name, $replacement, $version );
5873  
5874      /**
5875       * Filters whether to trigger an error for a deprecated class.
5876       *
5877       * @since 6.4.0
5878       *
5879       * @param bool $trigger Whether to trigger an error for a deprecated class. Default true.
5880       */
5881      if ( WP_DEBUG && apply_filters( 'deprecated_class_trigger_error', true ) ) {
5882          if ( function_exists( '__' ) ) {
5883              if ( $replacement ) {
5884                  $message = sprintf(
5885                      /* translators: 1: PHP class name, 2: Version number, 3: Alternative class or function name. */
5886                      __( 'Class %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
5887                      $class_name,
5888                      $version,
5889                      $replacement
5890                  );
5891              } else {
5892                  $message = sprintf(
5893                      /* translators: 1: PHP class name, 2: Version number. */
5894                      __( 'Class %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
5895                      $class_name,
5896                      $version
5897                  );
5898              }
5899          } else {
5900              if ( $replacement ) {
5901                  $message = sprintf(
5902                      'Class %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
5903                      $class_name,
5904                      $version,
5905                      $replacement
5906                  );
5907              } else {
5908                  $message = sprintf(
5909                      'Class %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
5910                      $class_name,
5911                      $version
5912                  );
5913              }
5914          }
5915  
5916          wp_trigger_error( '', $message, E_USER_DEPRECATED );
5917      }
5918  }
5919  
5920  /**
5921   * Marks a file as deprecated and inform when it has been used.
5922   *
5923   * There is a {@see 'deprecated_file_included'} hook that will be called that can be used
5924   * to get the backtrace up to what file and function included the deprecated file.
5925   *
5926   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
5927   *
5928   * This function is to be used in every file that is deprecated.
5929   *
5930   * @since 2.5.0
5931   * @since 5.4.0 This function is no longer marked as "private".
5932   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
5933   *
5934   * @param string $file        The file that was included.
5935   * @param string $version     The version of WordPress that deprecated the file.
5936   * @param string $replacement Optional. The file that should have been included based on ABSPATH.
5937   *                            Default empty string.
5938   * @param string $message     Optional. A message regarding the change. Default empty string.
5939   */
5940  function _deprecated_file( $file, $version, $replacement = '', $message = '' ) {
5941  
5942      /**
5943       * Fires when a deprecated file is called.
5944       *
5945       * @since 2.5.0
5946       *
5947       * @param string $file        The file that was called.
5948       * @param string $replacement The file that should have been included based on ABSPATH.
5949       * @param string $version     The version of WordPress that deprecated the file.
5950       * @param string $message     A message regarding the change.
5951       */
5952      do_action( 'deprecated_file_included', $file, $replacement, $version, $message );
5953  
5954      /**
5955       * Filters whether to trigger an error for deprecated files.
5956       *
5957       * @since 2.5.0
5958       *
5959       * @param bool $trigger Whether to trigger the error for deprecated files. Default true.
5960       */
5961      if ( WP_DEBUG && apply_filters( 'deprecated_file_trigger_error', true ) ) {
5962          $message = empty( $message ) ? '' : ' ' . $message;
5963  
5964          if ( function_exists( '__' ) ) {
5965              if ( $replacement ) {
5966                  $message = sprintf(
5967                      /* translators: 1: PHP file name, 2: Version number, 3: Alternative file name. */
5968                      __( 'File %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
5969                      $file,
5970                      $version,
5971                      $replacement
5972                  ) . $message;
5973              } else {
5974                  $message = sprintf(
5975                      /* translators: 1: PHP file name, 2: Version number. */
5976                      __( 'File %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
5977                      $file,
5978                      $version
5979                  ) . $message;
5980              }
5981          } else {
5982              if ( $replacement ) {
5983                  $message = sprintf(
5984                      'File %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
5985                      $file,
5986                      $version,
5987                      $replacement
5988                  );
5989              } else {
5990                  $message = sprintf(
5991                      'File %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
5992                      $file,
5993                      $version
5994                  ) . $message;
5995              }
5996          }
5997  
5998          wp_trigger_error( '', $message, E_USER_DEPRECATED );
5999      }
6000  }
6001  
6002  /**
6003   * Marks a function argument as deprecated and inform when it has been used.
6004   *
6005   * This function is to be used whenever a deprecated function argument is used.
6006   * Before this function is called, the argument must be checked for whether it was
6007   * used by comparing it to its default value or evaluating whether it is empty.
6008   *
6009   * For example:
6010   *
6011   *     if ( ! empty( $deprecated ) ) {
6012   *         _deprecated_argument( __FUNCTION__, '3.0.0' );
6013   *     }
6014   *
6015   * There is a {@see 'deprecated_argument_run'} hook that will be called that can be used
6016   * to get the backtrace up to what file and function used the deprecated argument.
6017   *
6018   * The current behavior is to trigger a user error if WP_DEBUG is true.
6019   *
6020   * @since 3.0.0
6021   * @since 5.4.0 This function is no longer marked as "private".
6022   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
6023   *
6024   * @param string $function_name The function that was called.
6025   * @param string $version       The version of WordPress that deprecated the argument used.
6026   * @param string $message       Optional. A message regarding the change. Default empty string.
6027   */
6028  function _deprecated_argument( $function_name, $version, $message = '' ) {
6029  
6030      /**
6031       * Fires when a deprecated argument is called.
6032       *
6033       * @since 3.0.0
6034       *
6035       * @param string $function_name The function that was called.
6036       * @param string $message       A message regarding the change.
6037       * @param string $version       The version of WordPress that deprecated the argument used.
6038       */
6039      do_action( 'deprecated_argument_run', $function_name, $message, $version );
6040  
6041      /**
6042       * Filters whether to trigger an error for deprecated arguments.
6043       *
6044       * @since 3.0.0
6045       *
6046       * @param bool $trigger Whether to trigger the error for deprecated arguments. Default true.
6047       */
6048      if ( WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) {
6049          if ( function_exists( '__' ) ) {
6050              if ( $message ) {
6051                  $message = sprintf(
6052                      /* translators: 1: PHP function name, 2: Version number, 3: Optional message regarding the change. */
6053                      __( 'Function %1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s' ),
6054                      $function_name,
6055                      $version,
6056                      $message
6057                  );
6058              } else {
6059                  $message = sprintf(
6060                      /* translators: 1: PHP function name, 2: Version number. */
6061                      __( 'Function %1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
6062                      $function_name,
6063                      $version
6064                  );
6065              }
6066          } else {
6067              if ( $message ) {
6068                  $message = sprintf(
6069                      'Function %1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s',
6070                      $function_name,
6071                      $version,
6072                      $message
6073                  );
6074              } else {
6075                  $message = sprintf(
6076                      'Function %1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.',
6077                      $function_name,
6078                      $version
6079                  );
6080              }
6081          }
6082  
6083          wp_trigger_error( '', $message, E_USER_DEPRECATED );
6084      }
6085  }
6086  
6087  /**
6088   * Marks a deprecated action or filter hook as deprecated and throws a notice.
6089   *
6090   * Use the {@see 'deprecated_hook_run'} action to get the backtrace describing where
6091   * the deprecated hook was called.
6092   *
6093   * Default behavior is to trigger a user error if `WP_DEBUG` is true.
6094   *
6095   * This function is called by the do_action_deprecated() and apply_filters_deprecated()
6096   * functions, and so generally does not need to be called directly.
6097   *
6098   * @since 4.6.0
6099   * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE).
6100   * @access private
6101   *
6102   * @param string $hook        The hook that was used.
6103   * @param string $version     The version of WordPress that deprecated the hook.
6104   * @param string $replacement Optional. The hook that should have been used. Default empty string.
6105   * @param string $message     Optional. A message regarding the change. Default empty.
6106   */
6107  function _deprecated_hook( $hook, $version, $replacement = '', $message = '' ) {
6108      /**
6109       * Fires when a deprecated hook is called.
6110       *
6111       * @since 4.6.0
6112       *
6113       * @param string $hook        The hook that was called.
6114       * @param string $replacement The hook that should be used as a replacement.
6115       * @param string $version     The version of WordPress that deprecated the argument used.
6116       * @param string $message     A message regarding the change.
6117       */
6118      do_action( 'deprecated_hook_run', $hook, $replacement, $version, $message );
6119  
6120      /**
6121       * Filters whether to trigger deprecated hook errors.
6122       *
6123       * @since 4.6.0
6124       *
6125       * @param bool $trigger Whether to trigger deprecated hook errors. Requires
6126       *                      `WP_DEBUG` to be defined true.
6127       */
6128      if ( WP_DEBUG && apply_filters( 'deprecated_hook_trigger_error', true ) ) {
6129          $message = empty( $message ) ? '' : ' ' . $message;
6130  
6131          if ( $replacement ) {
6132              $message = sprintf(
6133                  /* translators: 1: WordPress hook name, 2: Version number, 3: Alternative hook name. */
6134                  __( 'Hook %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
6135                  $hook,
6136                  $version,
6137                  $replacement
6138              ) . $message;
6139          } else {
6140              $message = sprintf(
6141                  /* translators: 1: WordPress hook name, 2: Version number. */
6142                  __( 'Hook %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
6143                  $hook,
6144                  $version
6145              ) . $message;
6146          }
6147  
6148          wp_trigger_error( '', $message, E_USER_DEPRECATED );
6149      }
6150  }
6151  
6152  /**
6153   * Marks something as being incorrectly called.
6154   *
6155   * There is a {@see 'doing_it_wrong_run'} hook that will be called that can be used
6156   * to get the backtrace up to what file and function called the deprecated function.
6157   *
6158   * The current behavior is to trigger a user error if `WP_DEBUG` is true.
6159   *
6160   * @since 3.1.0
6161   * @since 5.4.0 This function is no longer marked as "private".
6162   *
6163   * @param string $function_name The function that was called.
6164   * @param string $message       A message explaining what has been done incorrectly.
6165   * @param string $version       The version of WordPress where the message was added.
6166   */
6167  function _doing_it_wrong( $function_name, $message, $version ) {
6168  
6169      /**
6170       * Fires when the given function is being used incorrectly.
6171       *
6172       * @since 3.1.0
6173       *
6174       * @param string $function_name The function that was called.
6175       * @param string $message       A message explaining what has been done incorrectly.
6176       * @param string $version       The version of WordPress where the message was added.
6177       */
6178      do_action( 'doing_it_wrong_run', $function_name, $message, $version );
6179  
6180      /**
6181       * Filters whether to trigger an error for _doing_it_wrong() calls.
6182       *
6183       * @since 3.1.0
6184       * @since 5.1.0 Added the `$function_name`, `$message`, and `$version` parameters.
6185       *
6186       * @param bool   $trigger       Whether to trigger the error for _doing_it_wrong() calls. Default true.
6187       * @param string $function_name The function that was called.
6188       * @param string $message       A message explaining what has been done incorrectly.
6189       * @param string $version       The version of WordPress where the message was added.
6190       */
6191      if ( WP_DEBUG && apply_filters( 'doing_it_wrong_trigger_error', true, $function_name, $message, $version ) ) {
6192          if ( function_exists( '__' ) ) {
6193              if ( $version ) {
6194                  /* translators: %s: Version number. */
6195                  $version = sprintf( __( '(This message was added in version %s.)' ), $version );
6196              }
6197  
6198              $message .= ' ' . sprintf(
6199                  /* translators: %s: Documentation URL. */
6200                  __( 'Please see <a href="%s">Debugging in WordPress</a> for more information.' ),
6201                  __( 'https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/' )
6202              );
6203  
6204              $message = sprintf(
6205                  /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: WordPress version number. */
6206                  __( 'Function %1$s was called <strong>incorrectly</strong>. %2$s %3$s' ),
6207                  $function_name,
6208                  $message,
6209                  $version
6210              );
6211          } else {
6212              if ( $version ) {
6213                  $version = sprintf( '(This message was added in version %s.)', $version );
6214              }
6215  
6216              $message .= sprintf(
6217                  ' Please see <a href="%s">Debugging in WordPress</a> for more information.',
6218                  'https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/'
6219              );
6220  
6221              $message = sprintf(
6222                  'Function %1$s was called <strong>incorrectly</strong>. %2$s %3$s',
6223                  $function_name,
6224                  $message,
6225                  $version
6226              );
6227          }
6228  
6229          wp_trigger_error( '', $message );
6230      }
6231  }
6232  
6233  /**
6234   * Generates a user-level error/warning/notice/deprecation message.
6235   *
6236   * Generates the message when `WP_DEBUG` is true.
6237   *
6238   * @since 6.4.0
6239   *
6240   * @param string $function_name The function that triggered the error.
6241   * @param string $message       The message explaining the error.
6242   *                              The message can contain allowed HTML 'a' (with href), 'code',
6243   *                              'br', 'em', and 'strong' tags and http or https protocols.
6244   *                              If it contains other HTML tags or protocols, the message should be escaped
6245   *                              before passing to this function to avoid being stripped {@see wp_kses()}.
6246   * @param int    $error_level   Optional. The designated error type for this error.
6247   *                              Only works with E_USER family of constants. Default E_USER_NOTICE.
6248   */
6249  function wp_trigger_error( $function_name, $message, $error_level = E_USER_NOTICE ) {
6250      /**
6251       * Always fires when the given function triggers a user-level error/warning/notice/deprecation message.
6252       *
6253       * Can be used to attach custom error handlers even if WP_DEBUG is not truthy.
6254       *
6255       * @since 7.0.0
6256       *
6257       * @param string $function_name The function that triggered the error.
6258       * @param string $message       The message explaining the error.
6259       * @param int    $error_level   The designated error type for this error.
6260       */
6261      do_action( 'wp_trigger_error_always_run', $function_name, $message, $error_level );
6262  
6263      /**
6264       * Filters whether to trigger an error.
6265       *
6266       * @since 7.0.0
6267       *
6268       * @param bool   $trigger       Whether to trigger the error. Default true.
6269       * @param string $function_name The function that triggered the error.
6270       * @param string $message       The message explaining the error.
6271       * @param int    $error_level   The designated error type for this error.
6272       */
6273      if ( ! apply_filters( 'wp_trigger_error_trigger_error', true, $function_name, $message, $error_level ) ) {
6274          return;
6275      }
6276  
6277      // Bail out if WP_DEBUG is not turned on.
6278      if ( ! WP_DEBUG ) {
6279          return;
6280      }
6281  
6282      /**
6283       * Fires when the given function triggers a user-level error/warning/notice/deprecation message.
6284       *
6285       * Can be used for debug backtracking.
6286       *
6287       * @since 6.4.0
6288       *
6289       * @param string $function_name The function that triggered the error.
6290       * @param string $message       The message explaining the error.
6291       * @param int    $error_level   The designated error type for this error.
6292       */
6293      do_action( 'wp_trigger_error_run', $function_name, $message, $error_level );
6294  
6295      if ( ! empty( $function_name ) ) {
6296          $message = sprintf( '%s(): %s', $function_name, $message );
6297      }
6298  
6299      $message = wp_kses(
6300          $message,
6301          array(
6302              'a'      => array( 'href' => true ),
6303              'br'     => array(),
6304              'code'   => array(),
6305              'em'     => array(),
6306              'strong' => array(),
6307          ),
6308          array( 'http', 'https' )
6309      );
6310  
6311      if ( E_USER_ERROR === $error_level ) {
6312          throw new WP_Exception( $message );
6313      }
6314  
6315      trigger_error( $message, $error_level );
6316  }
6317  
6318  /**
6319   * Determines whether the server is running an earlier than 1.5.0 version of lighttpd.
6320   *
6321   * @since 2.5.0
6322   *
6323   * @return bool Whether the server is running lighttpd < 1.5.0.
6324   */
6325  function is_lighttpd_before_150() {
6326      $server_parts      = explode( '/', $_SERVER['SERVER_SOFTWARE'] ?? '' );
6327      $server_parts[1] ??= '';
6328  
6329      return ( 'lighttpd' === $server_parts[0] && -1 === version_compare( $server_parts[1], '1.5.0' ) );
6330  }
6331  
6332  /**
6333   * Determines whether the specified module exist in the Apache config.
6334   *
6335   * @since 2.5.0
6336   *
6337   * @global bool $is_apache
6338   *
6339   * @param string $mod           The module, e.g. mod_rewrite.
6340   * @param bool   $default_value Optional. The default return value if the module is not found. Default false.
6341   * @return bool Whether the specified module is loaded.
6342   */
6343  function apache_mod_loaded( $mod, $default_value = false ) {
6344      global $is_apache;
6345  
6346      if ( ! $is_apache ) {
6347          return false;
6348      }
6349  
6350      $loaded_mods = array();
6351  
6352      if ( function_exists( 'apache_get_modules' ) ) {
6353          $loaded_mods = apache_get_modules();
6354  
6355          if ( in_array( $mod, $loaded_mods, true ) ) {
6356              return true;
6357          }
6358      }
6359  
6360      if ( empty( $loaded_mods )
6361          && function_exists( 'phpinfo' )
6362          && ! str_contains( ini_get( 'disable_functions' ), 'phpinfo' )
6363      ) {
6364          ob_start();
6365          phpinfo( INFO_MODULES );
6366          $phpinfo = ob_get_clean();
6367  
6368          if ( str_contains( $phpinfo, $mod ) ) {
6369              return true;
6370          }
6371      }
6372  
6373      return $default_value;
6374  }
6375  
6376  /**
6377   * Checks if IIS 7+ supports pretty permalinks.
6378   *
6379   * @since 2.8.0
6380   *
6381   * @global bool $is_iis7
6382   *
6383   * @return bool Whether IIS7 supports permalinks.
6384   */
6385  function iis7_supports_permalinks() {
6386      global $is_iis7;
6387  
6388      $supports_permalinks = false;
6389      if ( $is_iis7 ) {
6390          /* First we check if the DOMDocument class exists. If it does not exist, then we cannot
6391           * easily update the xml configuration file, hence we just bail out and tell user that
6392           * pretty permalinks cannot be used.
6393           *
6394           * Next we check if the URL Rewrite Module 1.1 is loaded and enabled for the website. When
6395           * URL Rewrite 1.1 is loaded it always sets a server variable called 'IIS_UrlRewriteModule'.
6396           * Lastly we make sure that PHP is running via FastCGI. This is important because if it runs
6397           * via ISAPI then pretty permalinks will not work.
6398           */
6399          $supports_permalinks = class_exists( 'DOMDocument', false ) && isset( $_SERVER['IIS_UrlRewriteModule'] ) && ( 'cgi-fcgi' === PHP_SAPI );
6400      }
6401  
6402      /**
6403       * Filters whether IIS 7+ supports pretty permalinks.
6404       *
6405       * @since 2.8.0
6406       *
6407       * @param bool $supports_permalinks Whether IIS7 supports permalinks. Default false.
6408       */
6409      return apply_filters( 'iis7_supports_permalinks', $supports_permalinks );
6410  }
6411  
6412  /**
6413   * Validates a file name and path against an allowed set of rules.
6414   *
6415   * A return value of `1` means the file path contains directory traversal.
6416   *
6417   * A return value of `2` means the file path contains an absolute Windows path.
6418   * This covers drive paths such as `C:/WINDOWS`, UNC network share paths such as
6419   * `//server/share`, and the Windows device namespaces `//./` and `//?/`.
6420   *
6421   * A return value of `3` means the file is not in the allowed files list.
6422   *
6423   * A return value of `0` means no file was provided or processed.
6424   *
6425   * Note that absolute POSIX paths such as `/etc/passwd` are *not* rejected, and
6426   * never have been. Callers that must reject them are responsible for their own
6427   * check. The convention in core is to concatenate the validated value onto a
6428   * trusted base directory and then confirm the result exists, rather than to
6429   * treat this function as an absolute-path guard.
6430   *
6431   * @since 1.2.0
6432   * @since 7.2.0 A return value of `2` also covers UNC and Windows device paths.
6433   *
6434   * @param string   $file          File path.
6435   * @param string[] $allowed_files Optional. Array of allowed files. Default empty array.
6436   * @return int 0 means nothing is wrong, greater than 0 means something was wrong.
6437   *
6438   * @phpstan-return ( $file is '' ? 0 : ( $allowed_files is empty ? 0|1|2 : 0|1|2|3 ) )
6439   */
6440  function validate_file( $file, $allowed_files = array() ) {
6441      if ( ! is_scalar( $file ) || '' === $file ) {
6442          return 0;
6443      }
6444  
6445      // Normalize path for Windows servers.
6446      $file = wp_normalize_path( $file );
6447      // Normalize path for $allowed_files as well so it's an apples to apples comparison.
6448      $allowed_files = array_map( 'wp_normalize_path', $allowed_files );
6449  
6450      // `../` on its own is not allowed:
6451      if ( '../' === $file ) {
6452          return 1;
6453      }
6454  
6455      // More than one occurrence of `../` is not allowed:
6456      if ( preg_match_all( '#\.\./#', $file, $matches, PREG_SET_ORDER ) && ( count( $matches ) > 1 ) ) {
6457          return 1;
6458      }
6459  
6460      // `../` which does not occur at the end of the path is not allowed:
6461      if ( str_contains( $file, '../' ) && '../' !== mb_substr( $file, -3, 3 ) ) {
6462          return 1;
6463      }
6464  
6465      // Files not in the allowed file list are not allowed:
6466      if ( ! empty( $allowed_files ) && ! in_array( $file, $allowed_files, true ) ) {
6467          return 3;
6468      }
6469  
6470      /*
6471       * Absolute Windows paths are not allowed.
6472       *
6473       * The drive-letter test predates validate_file() itself, arriving from
6474       * b2/cafelog by way of a long series of moves. It only ever matched the
6475       * `X:` form, which left UNC and device paths accepted: wp_normalize_path()
6476       * above has already folded backslashes to forward slashes, and it
6477       * deliberately preserves a leading `//` for network shares, so those
6478       * paths arrive with no colon in the second byte.
6479       *
6480       * Anchoring the second test to the start of the string is what keeps
6481       * stream wrappers working. A registered wrapper keeps its `://` through
6482       * wp_normalize_path(), placing those slashes past the second byte.
6483       */
6484      if ( ':' === substr( $file, 1, 1 ) || str_starts_with( $file, '//' ) ) {
6485          return 2;
6486      }
6487  
6488      return 0;
6489  }
6490  
6491  /**
6492   * Determines whether to force SSL used for the Administration Screens.
6493   *
6494   * @since 2.6.0
6495   *
6496   * @param string|bool|null $force Optional. Whether to force SSL in admin screens. Default null.
6497   * @return bool True if forced, false if not forced.
6498   */
6499  function force_ssl_admin( $force = null ) {
6500      static $forced = false;
6501  
6502      if ( ! is_null( $force ) ) {
6503          $old_forced = $forced;
6504          $forced     = (bool) $force;
6505          return $old_forced;
6506      }
6507  
6508      return $forced;
6509  }
6510  
6511  /**
6512   * Guesses the URL for the site.
6513   *
6514   * Will remove wp-admin links to retrieve only return URLs not in the wp-admin
6515   * directory.
6516   *
6517   * @since 2.6.0
6518   *
6519   * @return string The guessed URL.
6520   */
6521  function wp_guess_url() {
6522      if ( defined( 'WP_SITEURL' ) && '' !== WP_SITEURL ) {
6523          $url = WP_SITEURL;
6524      } else {
6525          $abspath_fix         = str_replace( '\\', '/', ABSPATH );
6526          $script_filename_dir = dirname( $_SERVER['SCRIPT_FILENAME'] );
6527  
6528          // The request is for the admin.
6529          if ( str_contains( $_SERVER['REQUEST_URI'], 'wp-admin' ) || str_contains( $_SERVER['REQUEST_URI'], 'wp-login.php' ) ) {
6530              $path = preg_replace( '#/(wp-admin/?.*|wp-login\.php.*)#i', '', $_SERVER['REQUEST_URI'] );
6531  
6532              // The request is for a file in ABSPATH.
6533          } elseif ( $script_filename_dir . '/' === $abspath_fix ) {
6534              // Strip off any file/query params in the path.
6535              $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['PHP_SELF'] );
6536  
6537          } else {
6538              if ( str_contains( $_SERVER['SCRIPT_FILENAME'], $abspath_fix ) ) {
6539                  // Request is hitting a file inside ABSPATH.
6540                  $directory = str_replace( ABSPATH, '', $script_filename_dir );
6541                  // Strip off the subdirectory, and any file/query params.
6542                  $path = preg_replace( '#/' . preg_quote( $directory, '#' ) . '/[^/]*$#i', '', $_SERVER['REQUEST_URI'] );
6543              } elseif ( str_contains( $abspath_fix, $script_filename_dir ) ) {
6544                  // Request is hitting a file above ABSPATH.
6545                  $subdirectory = substr( $abspath_fix, strpos( $abspath_fix, $script_filename_dir ) + strlen( $script_filename_dir ) );
6546                  // Strip off any file/query params from the path, appending the subdirectory to the installation.
6547                  $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['REQUEST_URI'] ) . $subdirectory;
6548              } else {
6549                  $path = $_SERVER['REQUEST_URI'];
6550              }
6551          }
6552  
6553          $schema = is_ssl() ? 'https://' : 'http://'; // set_url_scheme() is not defined yet.
6554          $url    = $schema . $_SERVER['HTTP_HOST'] . $path;
6555      }
6556  
6557      return rtrim( $url, '/' );
6558  }
6559  
6560  /**
6561   * Temporarily suspends cache additions.
6562   *
6563   * Stops more data being added to the cache, but still allows cache retrieval.
6564   * This is useful for actions, such as imports, when a lot of data would otherwise
6565   * be almost uselessly added to the cache.
6566   *
6567   * Suspension lasts for a single page load at most. Remember to call this
6568   * function again if you wish to re-enable cache adds earlier.
6569   *
6570   * @since 3.3.0
6571   *
6572   * @param bool $suspend Optional. Suspends additions if true, re-enables them if false.
6573   *                      Defaults to not changing the current setting.
6574   * @return bool The current suspend setting.
6575   */
6576  function wp_suspend_cache_addition( $suspend = null ) {
6577      static $_suspend = false;
6578  
6579      if ( is_bool( $suspend ) ) {
6580          $_suspend = $suspend;
6581      }
6582  
6583      return $_suspend;
6584  }
6585  
6586  /**
6587   * Suspends cache invalidation.
6588   *
6589   * Turns cache invalidation on and off. Useful during imports where you don't want to do
6590   * invalidations every time a post is inserted. Callers must be sure that what they are
6591   * doing won't lead to an inconsistent cache when invalidation is suspended.
6592   *
6593   * @since 2.7.0
6594   *
6595   * @global bool $_wp_suspend_cache_invalidation
6596   *
6597   * @param bool $suspend Optional. Whether to suspend or enable cache invalidation. Default true.
6598   * @return bool The current suspend setting.
6599   */
6600  function wp_suspend_cache_invalidation( $suspend = true ) {
6601      global $_wp_suspend_cache_invalidation;
6602  
6603      $current_suspend                = $_wp_suspend_cache_invalidation;
6604      $_wp_suspend_cache_invalidation = $suspend;
6605      return $current_suspend;
6606  }
6607  
6608  /**
6609   * Determines whether a site is the main site of the current network.
6610   *
6611   * @since 3.0.0
6612   * @since 4.9.0 The `$network_id` parameter was added.
6613   *
6614   * @param int $site_id    Optional. Site ID to test. Defaults to current site.
6615   * @param int $network_id Optional. Network ID of the network to check for.
6616   *                        Defaults to current network.
6617   * @return bool True if $site_id is the main site of the network, or if not
6618   *              running Multisite.
6619   */
6620  function is_main_site( $site_id = null, $network_id = null ) {
6621      if ( ! is_multisite() ) {
6622          return true;
6623      }
6624  
6625      if ( ! $site_id ) {
6626          $site_id = get_current_blog_id();
6627      }
6628  
6629      $site_id = (int) $site_id;
6630  
6631      return get_main_site_id( $network_id ) === $site_id;
6632  }
6633  
6634  /**
6635   * Gets the main site ID.
6636   *
6637   * @since 4.9.0
6638   *
6639   * @param int $network_id Optional. The ID of the network for which to get the main site.
6640   *                        Defaults to the current network.
6641   * @return int The ID of the main site.
6642   */
6643  function get_main_site_id( $network_id = null ) {
6644      if ( ! is_multisite() ) {
6645          return get_current_blog_id();
6646      }
6647  
6648      $network = get_network( $network_id );
6649      if ( ! $network ) {
6650          return 0;
6651      }
6652  
6653      return $network->site_id;
6654  }
6655  
6656  /**
6657   * Determines whether a network is the main network of the Multisite installation.
6658   *
6659   * @since 3.7.0
6660   *
6661   * @param int $network_id Optional. Network ID to test. Defaults to current network.
6662   * @return bool True if $network_id is the main network, or if not running Multisite.
6663   */
6664  function is_main_network( $network_id = null ) {
6665      if ( ! is_multisite() ) {
6666          return true;
6667      }
6668  
6669      if ( null === $network_id ) {
6670          $network_id = get_current_network_id();
6671      }
6672  
6673      $network_id = (int) $network_id;
6674  
6675      return ( get_main_network_id() === $network_id );
6676  }
6677  
6678  /**
6679   * Gets the main network ID.
6680   *
6681   * @since 4.3.0
6682   *
6683   * @return int The ID of the main network.
6684   */
6685  function get_main_network_id() {
6686      if ( ! is_multisite() ) {
6687          return 1;
6688      }
6689  
6690      $current_network = get_network();
6691  
6692      if ( defined( 'PRIMARY_NETWORK_ID' ) ) {
6693          $main_network_id = PRIMARY_NETWORK_ID;
6694      } elseif ( isset( $current_network->id ) && 1 === (int) $current_network->id ) {
6695          // If the current network has an ID of 1, assume it is the main network.
6696          $main_network_id = 1;
6697      } else {
6698          $_networks       = get_networks(
6699              array(
6700                  'fields' => 'ids',
6701                  'number' => 1,
6702              )
6703          );
6704          $main_network_id = array_shift( $_networks );
6705      }
6706  
6707      /**
6708       * Filters the main network ID.
6709       *
6710       * @since 4.3.0
6711       *
6712       * @param int $main_network_id The ID of the main network.
6713       */
6714      return (int) apply_filters( 'get_main_network_id', $main_network_id );
6715  }
6716  
6717  /**
6718   * Determines whether site meta is enabled.
6719   *
6720   * This function checks whether the 'blogmeta' database table exists. The result is saved as
6721   * a setting for the main network, making it essentially a global setting. Subsequent requests
6722   * will refer to this setting instead of running the query.
6723   *
6724   * @since 5.1.0
6725   *
6726   * @global wpdb $wpdb WordPress database abstraction object.
6727   *
6728   * @return bool True if site meta is supported, false otherwise.
6729   */
6730  function is_site_meta_supported() {
6731      global $wpdb;
6732  
6733      if ( ! is_multisite() ) {
6734          return false;
6735      }
6736  
6737      $network_id = get_main_network_id();
6738  
6739      $supported = get_network_option( $network_id, 'site_meta_supported', false );
6740      if ( false === $supported ) {
6741          $supported = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->blogmeta}'" ) ? 1 : 0;
6742  
6743          update_network_option( $network_id, 'site_meta_supported', $supported );
6744      }
6745  
6746      return (bool) $supported;
6747  }
6748  
6749  /**
6750   * Modifies gmt_offset for smart timezone handling.
6751   *
6752   * Overrides the gmt_offset option if we have a timezone_string available.
6753   *
6754   * @since 2.8.0
6755   *
6756   * @return float|false Timezone GMT offset, false otherwise.
6757   */
6758  function wp_timezone_override_offset() {
6759      $timezone_string = get_option( 'timezone_string' );
6760      if ( ! $timezone_string ) {
6761          return false;
6762      }
6763  
6764      $timezone_object = timezone_open( $timezone_string );
6765      $datetime_object = date_create();
6766      if ( false === $timezone_object || false === $datetime_object ) {
6767          return false;
6768      }
6769  
6770      return round( timezone_offset_get( $timezone_object, $datetime_object ) / HOUR_IN_SECONDS, 2 );
6771  }
6772  
6773  /**
6774   * Sort-helper for timezones.
6775   *
6776   * @since 2.9.0
6777   * @access private
6778   *
6779   * @param array $a
6780   * @param array $b
6781   * @return int Comparison result.
6782   */
6783  function _wp_timezone_choice_usort_callback( $a, $b ) {
6784      // Don't use translated versions of Etc.
6785      if ( 'Etc' === $a['continent'] && 'Etc' === $b['continent'] ) {
6786          // Make the order of these more like the old dropdown.
6787          if ( str_starts_with( $a['city'], 'GMT+' ) && str_starts_with( $b['city'], 'GMT+' ) ) {
6788              return -1 * ( strnatcasecmp( $a['city'], $b['city'] ) );
6789          }
6790  
6791          if ( 'UTC' === $a['city'] ) {
6792              if ( str_starts_with( $b['city'], 'GMT+' ) ) {
6793                  return 1;
6794              }
6795  
6796              return -1;
6797          }
6798  
6799          if ( 'UTC' === $b['city'] ) {
6800              if ( str_starts_with( $a['city'], 'GMT+' ) ) {
6801                  return -1;
6802              }
6803  
6804              return 1;
6805          }
6806  
6807          return strnatcasecmp( $a['city'], $b['city'] );
6808      }
6809  
6810      if ( $a['t_continent'] === $b['t_continent'] ) {
6811          if ( $a['t_city'] === $b['t_city'] ) {
6812              return strnatcasecmp( $a['t_subcity'], $b['t_subcity'] );
6813          }
6814  
6815          return strnatcasecmp( $a['t_city'], $b['t_city'] );
6816      } else {
6817          // Force Etc to the bottom of the list.
6818          if ( 'Etc' === $a['continent'] ) {
6819              return 1;
6820          }
6821  
6822          if ( 'Etc' === $b['continent'] ) {
6823              return -1;
6824          }
6825  
6826          return strnatcasecmp( $a['t_continent'], $b['t_continent'] );
6827      }
6828  }
6829  
6830  /**
6831   * Gives a nicely-formatted list of timezone strings.
6832   *
6833   * @since 2.9.0
6834   * @since 4.7.0 Added the `$locale` parameter.
6835   *
6836   * @param string $selected_zone Selected timezone.
6837   * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
6838   * @return string HTML select element for timezones.
6839   */
6840  function wp_timezone_choice( $selected_zone, $locale = null ) {
6841      static $mo_loaded = false, $locale_loaded = null;
6842  
6843      $continents = array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' );
6844  
6845      // Load translations for continents and cities.
6846      if ( ! $mo_loaded || $locale !== $locale_loaded ) {
6847          $locale_loaded = $locale ? $locale : get_locale();
6848          $mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
6849          unload_textdomain( 'continents-cities', true );
6850          load_textdomain( 'continents-cities', $mofile, $locale_loaded );
6851          $mo_loaded = true;
6852      }
6853  
6854      $tz_identifiers = timezone_identifiers_list();
6855      $zonen          = array();
6856  
6857      foreach ( $tz_identifiers as $zone ) {
6858          $zone = explode( '/', $zone );
6859          if ( ! in_array( $zone[0], $continents, true ) ) {
6860              continue;
6861          }
6862  
6863          // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later.
6864          $exists    = array(
6865              0 => ( isset( $zone[0] ) && $zone[0] ),
6866              1 => ( isset( $zone[1] ) && $zone[1] ),
6867              2 => ( isset( $zone[2] ) && $zone[2] ),
6868          );
6869          $exists[3] = ( $exists[0] && 'Etc' !== $zone[0] );
6870          $exists[4] = ( $exists[1] && $exists[3] );
6871          $exists[5] = ( $exists[2] && $exists[3] );
6872  
6873          // phpcs:disable WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText
6874          $zonen[] = array(
6875              'continent'   => ( $exists[0] ? $zone[0] : '' ),
6876              'city'        => ( $exists[1] ? $zone[1] : '' ),
6877              'subcity'     => ( $exists[2] ? $zone[2] : '' ),
6878              't_continent' => ( $exists[3] ? translate( str_replace( '_', ' ', $zone[0] ), 'continents-cities' ) : '' ),
6879              't_city'      => ( $exists[4] ? translate( str_replace( '_', ' ', $zone[1] ), 'continents-cities' ) : '' ),
6880              't_subcity'   => ( $exists[5] ? translate( str_replace( '_', ' ', $zone[2] ), 'continents-cities' ) : '' ),
6881          );
6882          // phpcs:enable
6883      }
6884      usort( $zonen, '_wp_timezone_choice_usort_callback' );
6885  
6886      $structure = array();
6887  
6888      if ( empty( $selected_zone ) ) {
6889          $structure[] = '<option selected="selected" value="">' . __( 'Select a city' ) . '</option>';
6890      }
6891  
6892      // If this is a deprecated, but valid, timezone string, display it at the top of the list as-is.
6893      if ( in_array( $selected_zone, $tz_identifiers, true ) === false
6894          && in_array( $selected_zone, timezone_identifiers_list( DateTimeZone::ALL_WITH_BC ), true )
6895      ) {
6896          $structure[] = '<option selected="selected" value="' . esc_attr( $selected_zone ) . '" dir="auto">' . esc_html( $selected_zone ) . '</option>';
6897      }
6898  
6899      foreach ( $zonen as $key => $zone ) {
6900          // Build value in an array to join later.
6901          $value = array( $zone['continent'] );
6902  
6903          if ( empty( $zone['city'] ) ) {
6904              // It's at the continent level (generally won't happen).
6905              $display = $zone['t_continent'];
6906          } else {
6907              // It's inside a continent group.
6908  
6909              // Continent optgroup.
6910              if ( ! isset( $zonen[ $key - 1 ] ) || $zonen[ $key - 1 ]['continent'] !== $zone['continent'] ) {
6911                  $label       = $zone['t_continent'];
6912                  $structure[] = '<optgroup label="' . esc_attr( $label ) . '" dir="auto">';
6913              }
6914  
6915              // Add the city to the value.
6916              $value[] = $zone['city'];
6917  
6918              $display = $zone['t_city'];
6919              if ( ! empty( $zone['subcity'] ) ) {
6920                  // Add the subcity to the value.
6921                  $value[]  = $zone['subcity'];
6922                  $display .= ' - ' . $zone['t_subcity'];
6923              }
6924          }
6925  
6926          // Build the value.
6927          $value    = implode( '/', $value );
6928          $selected = '';
6929          if ( $value === $selected_zone ) {
6930              $selected = 'selected="selected" ';
6931          }
6932          $structure[] = '<option ' . $selected . 'value="' . esc_attr( $value ) . '" dir="auto">' . esc_html( $display ) . '</option>';
6933  
6934          // Close continent optgroup.
6935          if ( ! empty( $zone['city'] ) && ( ! isset( $zonen[ $key + 1 ] ) || ( isset( $zonen[ $key + 1 ] ) && $zonen[ $key + 1 ]['continent'] !== $zone['continent'] ) ) ) {
6936              $structure[] = '</optgroup>';
6937          }
6938      }
6939  
6940      // Do UTC.
6941      $structure[] = '<optgroup label="' . esc_attr__( 'UTC' ) . '" dir="auto">';
6942      $selected    = '';
6943      if ( 'UTC' === $selected_zone ) {
6944          $selected = 'selected="selected" ';
6945      }
6946      $structure[] = '<option ' . $selected . 'value="' . esc_attr( 'UTC' ) . '" dir="auto">' . __( 'UTC' ) . '</option>';
6947      $structure[] = '</optgroup>';
6948  
6949      // Do manual UTC offsets.
6950      $structure[]  = '<optgroup label="' . esc_attr__( 'Manual Offsets' ) . '" dir="auto">';
6951      $offset_range = array(
6952          -12,
6953          -11.5,
6954          -11,
6955          -10.5,
6956          -10,
6957          -9.5,
6958          -9,
6959          -8.5,
6960          -8,
6961          -7.5,
6962          -7,
6963          -6.5,
6964          -6,
6965          -5.5,
6966          -5,
6967          -4.5,
6968          -4,
6969          -3.5,
6970          -3,
6971          -2.5,
6972          -2,
6973          -1.5,
6974          -1,
6975          -0.5,
6976          0,
6977          0.5,
6978          1,
6979          1.5,
6980          2,
6981          2.5,
6982          3,
6983          3.5,
6984          4,
6985          4.5,
6986          5,
6987          5.5,
6988          5.75,
6989          6,
6990          6.5,
6991          7,
6992          7.5,
6993          8,
6994          8.5,
6995          8.75,
6996          9,
6997          9.5,
6998          10,
6999          10.5,
7000          11,
7001          11.5,
7002          12,
7003          12.75,
7004          13,
7005          13.75,
7006          14,
7007      );
7008      foreach ( $offset_range as $offset ) {
7009          if ( 0 <= $offset ) {
7010              $offset_name = '+' . $offset;
7011          } else {
7012              $offset_name = (string) $offset;
7013          }
7014  
7015          $offset_value = $offset_name;
7016          $offset_name  = str_replace( array( '.25', '.5', '.75' ), array( ':15', ':30', ':45' ), $offset_name );
7017          $offset_name  = 'UTC' . $offset_name;
7018          $offset_value = 'UTC' . $offset_value;
7019          $selected     = '';
7020          if ( $offset_value === $selected_zone ) {
7021              $selected = 'selected="selected" ';
7022          }
7023          $structure[] = '<option ' . $selected . 'value="' . esc_attr( $offset_value ) . '" dir="auto">' . esc_html( $offset_name ) . '</option>';
7024      }
7025      $structure[] = '</optgroup>';
7026  
7027      return implode( "\n", $structure );
7028  }
7029  
7030  /**
7031   * Strips close comment and close php tags from file headers used by WP.
7032   *
7033   * @since 2.8.0
7034   * @access private
7035   *
7036   * @see https://core.trac.wordpress.org/ticket/8497
7037   *
7038   * @param string $str Header comment to clean up.
7039   * @return string Cleaned header comment.
7040   */
7041  function _cleanup_header_comment( $str ) {
7042      return trim( preg_replace( '/\s*(?:\*\/|\?>).*/', '', $str ) );
7043  }
7044  
7045  /**
7046   * Permanently deletes comments or posts of any type that have held a status
7047   * of 'trash' for the number of days defined in EMPTY_TRASH_DAYS.
7048   *
7049   * The default value of `EMPTY_TRASH_DAYS` is 30 (days).
7050   *
7051   * @since 2.9.0
7052   *
7053   * @global wpdb $wpdb WordPress database abstraction object.
7054   */
7055  function wp_scheduled_delete() {
7056      global $wpdb;
7057  
7058      $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
7059  
7060      $posts_to_delete = $wpdb->get_results( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < %d", $delete_timestamp ), ARRAY_A );
7061  
7062      foreach ( (array) $posts_to_delete as $post ) {
7063          $post_id = (int) $post['post_id'];
7064          if ( ! $post_id ) {
7065              continue;
7066          }
7067  
7068          $del_post = get_post( $post_id );
7069  
7070          if ( ! $del_post || 'trash' !== $del_post->post_status ) {
7071              delete_post_meta( $post_id, '_wp_trash_meta_status' );
7072              delete_post_meta( $post_id, '_wp_trash_meta_time' );
7073          } else {
7074              wp_delete_post( $post_id );
7075          }
7076      }
7077  
7078      $comments_to_delete = $wpdb->get_results( $wpdb->prepare( "SELECT comment_id FROM $wpdb->commentmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < %d", $delete_timestamp ), ARRAY_A );
7079  
7080      foreach ( (array) $comments_to_delete as $comment ) {
7081          $comment_id = (int) $comment['comment_id'];
7082          if ( ! $comment_id ) {
7083              continue;
7084          }
7085  
7086          $del_comment = get_comment( $comment_id );
7087  
7088          if ( ! $del_comment || 'trash' !== $del_comment->comment_approved ) {
7089              delete_comment_meta( $comment_id, '_wp_trash_meta_time' );
7090              delete_comment_meta( $comment_id, '_wp_trash_meta_status' );
7091          } else {
7092              wp_delete_comment( $del_comment );
7093          }
7094      }
7095  }
7096  
7097  /**
7098   * Retrieves metadata from a file.
7099   *
7100   * Searches for metadata in the first 8 KB of a file, such as a plugin or theme.
7101   * Each piece of metadata must be on its own line. Fields can not span multiple
7102   * lines, the value will get cut at the end of the first line.
7103   *
7104   * If the file data is not within that first 8 KB, then the author should correct
7105   * their plugin file and move the data headers to the top.
7106   *
7107   * @link https://codex.wordpress.org/File_Header
7108   *
7109   * @since 2.9.0
7110   *
7111   * @param string $file            Absolute path to the file.
7112   * @param array  $default_headers List of headers, in the format `array( 'HeaderKey' => 'Header Name' )`.
7113   * @param string $context         Optional. If specified adds filter hook {@see 'extra_$context_headers'}.
7114   *                                Default empty string.
7115   * @return string[] Array of file header values keyed by header name.
7116   */
7117  function get_file_data( $file, $default_headers, $context = '' ) {
7118      // Pull only the first 8 KB of the file in.
7119      $file_data = file_get_contents( $file, false, null, 0, 8 * KB_IN_BYTES );
7120  
7121      if ( false === $file_data ) {
7122          $file_data = '';
7123      }
7124  
7125      // Make sure we catch CR-only line endings.
7126      $file_data = str_replace( "\r", "\n", $file_data );
7127  
7128      /**
7129       * Filters extra file headers by context.
7130       *
7131       * The dynamic portion of the hook name, `$context`, refers to
7132       * the context where extra headers might be loaded.
7133       *
7134       * @since 2.9.0
7135       *
7136       * @param array $extra_context_headers Empty array by default.
7137       */
7138      $extra_headers = $context ? apply_filters( "extra_{$context}_headers", array() ) : array();
7139      if ( $extra_headers ) {
7140          $extra_headers = array_combine( $extra_headers, $extra_headers ); // Keys equal values.
7141          $all_headers   = array_merge( $extra_headers, (array) $default_headers );
7142      } else {
7143          $all_headers = $default_headers;
7144      }
7145  
7146      foreach ( $all_headers as $field => $regex ) {
7147          if ( preg_match( '/^(?:[ \t]*<\?(?:php)?)?[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_data, $match ) && $match[1] ) {
7148              $all_headers[ $field ] = _cleanup_header_comment( $match[1] );
7149          } else {
7150              $all_headers[ $field ] = '';
7151          }
7152      }
7153  
7154      return $all_headers;
7155  }
7156  
7157  /**
7158   * Returns true.
7159   *
7160   * Useful for returning true to filters easily.
7161   *
7162   * @since 3.0.0
7163   *
7164   * @see __return_false()
7165   *
7166   * @return true True.
7167   */
7168  function __return_true() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
7169      return true;
7170  }
7171  
7172  /**
7173   * Returns false.
7174   *
7175   * Useful for returning false to filters easily.
7176   *
7177   * @since 3.0.0
7178   *
7179   * @see __return_true()
7180   *
7181   * @return false False.
7182   */
7183  function __return_false() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
7184      return false;
7185  }
7186  
7187  /**
7188   * Returns 0.
7189   *
7190   * Useful for returning 0 to filters easily.
7191   *
7192   * @since 3.0.0
7193   *
7194   * @return int 0.
7195   */
7196  function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
7197      return 0;
7198  }
7199  
7200  /**
7201   * Returns an empty array.
7202   *
7203   * Useful for returning an empty array to filters easily.
7204   *
7205   * @since 3.0.0
7206   *
7207   * @return array Empty array.
7208   */
7209  function __return_empty_array() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
7210      return array();
7211  }
7212  
7213  /**
7214   * Returns null.
7215   *
7216   * Useful for returning null to filters easily.
7217   *
7218   * @since 3.4.0
7219   *
7220   * @return null Null value.
7221   */
7222  function __return_null() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
7223      return null;
7224  }
7225  
7226  /**
7227   * Returns an empty string.
7228   *
7229   * Useful for returning an empty string to filters easily.
7230   *
7231   * @since 3.7.0
7232   *
7233   * @see __return_null()
7234   *
7235   * @return string Empty string.
7236   */
7237  function __return_empty_string() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
7238      return '';
7239  }
7240  
7241  /**
7242   * Sends a HTTP header to disable content type sniffing in browsers which support it.
7243   *
7244   * @since 3.0.0
7245   *
7246   * @see https://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
7247   * @see https://src.chromium.org/viewvc/chrome?view=rev&revision=6985
7248   */
7249  function send_nosniff_header() {
7250      header( 'X-Content-Type-Options: nosniff' );
7251  }
7252  
7253  /**
7254   * Returns a MySQL expression for selecting the week number based on the start_of_week option.
7255   *
7256   * @ignore
7257   * @since 3.0.0
7258   *
7259   * @param string $column Database column.
7260   * @return string SQL clause.
7261   */
7262  function _wp_mysql_week( $column ) {
7263      $start_of_week = (int) get_option( 'start_of_week' );
7264      switch ( $start_of_week ) {
7265          case 1:
7266              return "WEEK( $column, 1 )";
7267          case 2:
7268          case 3:
7269          case 4:
7270          case 5:
7271          case 6:
7272              return "WEEK( DATE_SUB( $column, INTERVAL $start_of_week DAY ), 0 )";
7273          case 0:
7274          default:
7275              return "WEEK( $column, 0 )";
7276      }
7277  }
7278  
7279  /**
7280   * Finds hierarchy loops using a callback function that maps object IDs to parent IDs.
7281   *
7282   * @since 3.1.0
7283   * @access private
7284   *
7285   * @param callable $callback      Function that accepts ( ID, $callback_args ) and outputs parent_ID.
7286   * @param int      $start         The ID to start the loop check at.
7287   * @param int      $start_parent  The parent_ID of $start to use instead of calling $callback( $start ).
7288   *                                Use null to always use $callback.
7289   * @param array    $callback_args Optional. Additional arguments to send to $callback. Default empty array.
7290   * @return array IDs of all members of loop.
7291   */
7292  function wp_find_hierarchy_loop( $callback, $start, $start_parent, $callback_args = array() ) {
7293      $override = is_null( $start_parent ) ? array() : array( $start => $start_parent );
7294  
7295      $arbitrary_loop_member = wp_find_hierarchy_loop_tortoise_hare( $callback, $start, $override, $callback_args );
7296      if ( ! $arbitrary_loop_member ) {
7297          return array();
7298      }
7299  
7300      return wp_find_hierarchy_loop_tortoise_hare( $callback, $arbitrary_loop_member, $override, $callback_args, true );
7301  }
7302  
7303  /**
7304   * Uses the "The Tortoise and the Hare" algorithm to detect loops.
7305   *
7306   * For every step of the algorithm, the hare takes two steps and the tortoise one.
7307   * If the hare ever laps the tortoise, there must be a loop.
7308   *
7309   * @since 3.1.0
7310   * @access private
7311   *
7312   * @param callable $callback      Function that accepts ( ID, callback_arg, ... ) and outputs parent_ID.
7313   * @param int      $start         The ID to start the loop check at.
7314   * @param array    $override      Optional. An array of ( ID => parent_ID, ... ) to use instead of $callback.
7315   *                                Default empty array.
7316   * @param array    $callback_args Optional. Additional arguments to send to $callback. Default empty array.
7317   * @param bool     $_return_loop  Optional. Return loop members or just detect presence of loop? Only set
7318   *                                to true if you already know the given $start is part of a loop (otherwise
7319   *                                the returned array might include branches). Default false.
7320   * @return mixed Scalar ID of some arbitrary member of the loop, or array of IDs of all members of loop if
7321   *               $_return_loop. False if no loop was found.
7322   *
7323   * @phpstan-return (
7324   *     $_return_loop is true ? array<array-key, true>|false : mixed
7325   * )
7326   */
7327  function wp_find_hierarchy_loop_tortoise_hare( $callback, $start, $override = array(), $callback_args = array(), $_return_loop = false ) {
7328      $tortoise        = $start;
7329      $hare            = $start;
7330      $evanescent_hare = $start;
7331      $return          = array();
7332  
7333      // Set evanescent_hare to one past hare. Increment hare two steps.
7334      while (
7335          $tortoise
7336      &&
7337          ( $evanescent_hare = $override[ $hare ] ?? call_user_func_array( $callback, array_merge( array( $hare ), $callback_args ) ) )
7338      &&
7339          ( $hare = $override[ $evanescent_hare ] ?? call_user_func_array( $callback, array_merge( array( $evanescent_hare ), $callback_args ) ) )
7340      ) {
7341          if ( $_return_loop ) {
7342              $return[ $tortoise ]        = true;
7343              $return[ $evanescent_hare ] = true;
7344              $return[ $hare ]            = true;
7345          }
7346  
7347          // Tortoise got lapped - must be a loop.
7348          if ( $tortoise === $evanescent_hare || $tortoise === $hare ) {
7349              return $_return_loop ? $return : $tortoise;
7350          }
7351  
7352          // Increment tortoise by one step.
7353          $tortoise = $override[ $tortoise ] ?? call_user_func_array( $callback, array_merge( array( $tortoise ), $callback_args ) );
7354      }
7355  
7356      return false;
7357  }
7358  
7359  /**
7360   * Sends a HTTP header to limit rendering of pages to same origin iframes.
7361   *
7362   * @since 3.1.3
7363   *
7364   * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options
7365   * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-ancestors
7366   */
7367  function send_frame_options_header() {
7368      if ( ! headers_sent() ) {
7369          header( 'X-Frame-Options: SAMEORIGIN' );
7370          header( "Content-Security-Policy: frame-ancestors 'self';" );
7371      }
7372  }
7373  
7374  /**
7375   * Sends a referrer policy header so referrers are not sent externally from administration screens.
7376   *
7377   * @since 4.9.0
7378   * @since 6.8.0 This function was moved from `wp-admin/includes/misc.php` to `wp-includes/functions.php`.
7379   */
7380  function wp_admin_headers() {
7381      $policy = 'strict-origin-when-cross-origin';
7382  
7383      /**
7384       * Filters the admin referrer policy header value.
7385       *
7386       * @since 4.9.0
7387       * @since 4.9.5 The default value was changed to 'strict-origin-when-cross-origin'.
7388       *
7389       * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy
7390       *
7391       * @param string $policy The admin referrer policy header value. Default 'strict-origin-when-cross-origin'.
7392       */
7393      $policy = apply_filters( 'admin_referrer_policy', $policy );
7394  
7395      header( sprintf( 'Referrer-Policy: %s', $policy ) );
7396  }
7397  
7398  /**
7399   * Retrieves a list of protocols to allow in HTML attributes.
7400   *
7401   * @since 3.3.0
7402   * @since 4.3.0 Added 'webcal' to the protocols array.
7403   * @since 4.7.0 Added 'urn' to the protocols array.
7404   * @since 5.3.0 Added 'sms' to the protocols array.
7405   * @since 5.6.0 Added 'irc6' and 'ircs' to the protocols array.
7406   *
7407   * @see wp_kses()
7408   * @see esc_url()
7409   *
7410   * @return string[] Array of allowed protocols. Defaults to an array containing 'http', 'https',
7411   *                  'ftp', 'ftps', 'mailto', 'news', 'irc', 'irc6', 'ircs', 'gopher', 'nntp', 'feed',
7412   *                  'telnet', 'mms', 'rtsp', 'sms', 'svn', 'tel', 'fax', 'xmpp', 'webcal', and 'urn'.
7413   *                  This covers all common link protocols, except for 'javascript' which should not
7414   *                  be allowed for untrusted users.
7415   */
7416  function wp_allowed_protocols() {
7417      static $protocols = array();
7418  
7419      if ( empty( $protocols ) ) {
7420          $protocols = array( 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'irc6', 'ircs', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'sms', 'svn', 'tel', 'fax', 'xmpp', 'webcal', 'urn' );
7421      }
7422  
7423      if ( ! did_action( 'wp_loaded' ) ) {
7424          /**
7425           * Filters the list of protocols allowed in HTML attributes.
7426           *
7427           * @since 3.0.0
7428           *
7429           * @param string[] $protocols Array of allowed protocols e.g. 'http', 'ftp', 'tel', and more.
7430           */
7431          $protocols = array_unique( (array) apply_filters( 'kses_allowed_protocols', $protocols ) );
7432      }
7433  
7434      return $protocols;
7435  }
7436  
7437  /**
7438   * Returns a comma-separated string or array of functions that have been called to get
7439   * to the current point in code.
7440   *
7441   * @since 3.4.0
7442   *
7443   * @see https://core.trac.wordpress.org/ticket/19589
7444   *
7445   * @param string $ignore_class Optional. A class to ignore all function calls within - useful
7446   *                             when you want to just give info about the callee. Default null.
7447   * @param int    $skip_frames  Optional. A number of stack frames to skip - useful for unwinding
7448   *                             back to the source of the issue. Default 0.
7449   * @param bool   $pretty       Optional. Whether you want a comma separated string instead of
7450   *                             the raw array returned. Default true.
7451   * @return string|array Either a string containing a reversed comma separated trace or an array
7452   *                      of individual calls.
7453   *
7454   * @phpstan-return ( $pretty is true ? string : list<string> )
7455   */
7456  function wp_debug_backtrace_summary( $ignore_class = null, $skip_frames = 0, $pretty = true ) {
7457      static $truncate_paths;
7458  
7459      $trace       = debug_backtrace( false );
7460      $caller      = array();
7461      $check_class = ! is_null( $ignore_class );
7462      ++$skip_frames; // Skip this function.
7463  
7464      if ( ! isset( $truncate_paths ) ) {
7465          $truncate_paths = array(
7466              wp_normalize_path( WP_CONTENT_DIR ),
7467              wp_normalize_path( ABSPATH ),
7468          );
7469      }
7470  
7471      foreach ( $trace as $call ) {
7472          if ( $skip_frames > 0 ) {
7473              --$skip_frames;
7474          } elseif ( isset( $call['class'] ) ) {
7475              if ( $check_class && $ignore_class === $call['class'] ) {
7476                  continue; // Filter out calls.
7477              }
7478  
7479              $caller[] = "{$call['class']}{$call['type']}{$call['function']}";
7480          } else {
7481              if ( in_array( $call['function'], array( 'do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array' ), true ) ) {
7482                  $caller[] = "{$call['function']}('{$call['args'][0]}')";
7483              } elseif ( in_array( $call['function'], array( 'include', 'include_once', 'require', 'require_once' ), true ) ) {
7484                  $filename = $call['args'][0] ?? '';
7485                  $caller[] = $call['function'] . "('" . str_replace( $truncate_paths, '', wp_normalize_path( $filename ) ) . "')";
7486              } else {
7487                  $caller[] = $call['function'];
7488              }
7489          }
7490      }
7491      if ( $pretty ) {
7492          return implode( ', ', array_reverse( $caller ) );
7493      } else {
7494          return $caller;
7495      }
7496  }
7497  
7498  /**
7499   * Retrieves IDs that are not already present in the cache.
7500   *
7501   * @since 3.4.0
7502   * @since 6.1.0 This function is no longer marked as "private".
7503   *
7504   * @param int[]  $object_ids  Array of IDs.
7505   * @param string $cache_group The cache group to check against.
7506   * @return int[] Array of IDs not present in the cache.
7507   */
7508  function _get_non_cached_ids( $object_ids, $cache_group ) {
7509      $object_ids = array_filter( $object_ids, '_validate_cache_id' );
7510      $object_ids = array_unique( array_map( 'intval', $object_ids ), SORT_NUMERIC );
7511  
7512      if ( empty( $object_ids ) ) {
7513          return array();
7514      }
7515  
7516      $non_cached_ids = array();
7517      $cache_values   = wp_cache_get_multiple( $object_ids, $cache_group );
7518  
7519      foreach ( $cache_values as $id => $value ) {
7520          if ( false === $value ) {
7521              $non_cached_ids[] = (int) $id;
7522          }
7523      }
7524  
7525      return $non_cached_ids;
7526  }
7527  
7528  /**
7529   * Checks whether the given cache ID is either an integer or an integer-like string.
7530   *
7531   * Both `16` and `"16"` are considered valid, other numeric types and numeric strings
7532   * (`16.3` and `"16.3"`) are considered invalid.
7533   *
7534   * @since 6.3.0
7535   *
7536   * @param mixed $object_id The cache ID to validate.
7537   * @return bool Whether the given $object_id is a valid cache ID.
7538   */
7539  function _validate_cache_id( $object_id ) {
7540      /*
7541       * filter_var() could be used here, but the `filter` PHP extension
7542       * is considered optional and may not be available.
7543       */
7544      if ( is_int( $object_id )
7545          || ( is_string( $object_id ) && (string) (int) $object_id === $object_id ) ) {
7546          return true;
7547      }
7548  
7549      /* translators: %s: The type of the given object ID. */
7550      $message = sprintf( __( 'Object ID must be an integer, %s given.' ), gettype( $object_id ) );
7551      _doing_it_wrong( '_get_non_cached_ids', $message, '6.3.0' );
7552  
7553      return false;
7554  }
7555  
7556  /**
7557   * Tests if the current device has the capability to upload files.
7558   *
7559   * @since 3.4.0
7560   * @access private
7561   *
7562   * @return bool Whether the device is able to upload files.
7563   */
7564  function _device_can_upload() {
7565      if ( ! wp_is_mobile() ) {
7566          return true;
7567      }
7568  
7569      $ua = $_SERVER['HTTP_USER_AGENT'];
7570  
7571      if ( str_contains( $ua, 'iPhone' )
7572          || str_contains( $ua, 'iPad' )
7573          || str_contains( $ua, 'iPod' ) ) {
7574              return preg_match( '#OS ([\d_]+) like Mac OS X#', $ua, $version ) && version_compare( $version[1], '6', '>=' );
7575      }
7576  
7577      return true;
7578  }
7579  
7580  /**
7581   * Tests if a given path is a stream URL
7582   *
7583   * @since 3.5.0
7584   *
7585   * @param string $path The resource path or URL.
7586   * @return bool True if the path is a stream URL.
7587   */
7588  function wp_is_stream( $path ) {
7589      $scheme_separator = strpos( $path, '://' );
7590  
7591      if ( false === $scheme_separator ) {
7592          // $path isn't a stream.
7593          return false;
7594      }
7595  
7596      $stream = substr( $path, 0, $scheme_separator );
7597  
7598      return in_array( $stream, stream_get_wrappers(), true );
7599  }
7600  
7601  /**
7602   * Tests if the supplied date is valid for the Gregorian calendar.
7603   *
7604   * @since 3.5.0
7605   *
7606   * @link https://www.php.net/manual/en/function.checkdate.php
7607   *
7608   * @param int    $month       Month number.
7609   * @param int    $day         Day number.
7610   * @param int    $year        Year number.
7611   * @param string $source_date The date to filter.
7612   * @return bool True if valid date, false if not valid date.
7613   */
7614  function wp_checkdate( $month, $day, $year, $source_date ) {
7615      $checkdate = false;
7616      if ( is_numeric( $month ) && is_numeric( $day ) && is_numeric( $year ) ) {
7617          $checkdate = checkdate( (int) $month, (int) $day, (int) $year );
7618      }
7619  
7620      /**
7621       * Filters whether the given date is valid for the Gregorian calendar.
7622       *
7623       * @since 3.5.0
7624       *
7625       * @param bool   $checkdate   Whether the given date is valid.
7626       * @param string $source_date Date to check.
7627       */
7628      return apply_filters( 'wp_checkdate', $checkdate, $source_date );
7629  }
7630  
7631  /**
7632   * Loads the auth check for monitoring whether the user is still logged in.
7633   *
7634   * Can be disabled with remove_action( 'admin_enqueue_scripts', 'wp_auth_check_load' );
7635   *
7636   * This is disabled for certain screens where a login screen could cause an
7637   * inconvenient interruption. A filter called {@see 'wp_auth_check_load'} can be used
7638   * for fine-grained control.
7639   *
7640   * @since 3.6.0
7641   */
7642  function wp_auth_check_load() {
7643      if ( ! is_admin() && ! is_user_logged_in() ) {
7644          return;
7645      }
7646  
7647      if ( defined( 'IFRAME_REQUEST' ) ) {
7648          return;
7649      }
7650  
7651      $screen = get_current_screen();
7652      $hidden = array( 'update', 'update-network', 'update-core', 'update-core-network', 'upgrade', 'upgrade-network', 'network' );
7653      $show   = ! in_array( $screen->id, $hidden, true );
7654  
7655      /**
7656       * Filters whether to load the authentication check.
7657       *
7658       * Returning a falsey value from the filter will effectively short-circuit
7659       * loading the authentication check.
7660       *
7661       * @since 3.6.0
7662       *
7663       * @param bool      $show   Whether to load the authentication check.
7664       * @param WP_Screen $screen The current screen object.
7665       */
7666      if ( apply_filters( 'wp_auth_check_load', $show, $screen ) ) {
7667          wp_enqueue_style( 'wp-auth-check' );
7668          wp_enqueue_script( 'wp-auth-check' );
7669  
7670          add_action( 'admin_print_footer_scripts', 'wp_auth_check_html', 5 );
7671          add_action( 'wp_print_footer_scripts', 'wp_auth_check_html', 5 );
7672      }
7673  }
7674  
7675  /**
7676   * Outputs the HTML that shows the wp-login dialog when the user is no longer logged in.
7677   *
7678   * @since 3.6.0
7679   */
7680  function wp_auth_check_html() {
7681      $login_url      = wp_login_url();
7682      $current_domain = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'];
7683      $same_domain    = str_starts_with( $login_url, $current_domain );
7684  
7685      /**
7686       * Filters whether the authentication check originated at the same domain.
7687       *
7688       * @since 3.6.0
7689       *
7690       * @param bool $same_domain Whether the authentication check originated at the same domain.
7691       */
7692      $same_domain = apply_filters( 'wp_auth_check_same_domain', $same_domain );
7693      $wrap_class  = $same_domain ? 'hidden' : 'hidden fallback';
7694  
7695      ?>
7696      <div id="wp-auth-check-wrap" class="<?php echo $wrap_class; ?>">
7697      <div id="wp-auth-check-bg"></div>
7698      <div id="wp-auth-check">
7699      <button type="button" class="wp-auth-check-close button-link"><span class="screen-reader-text">
7700          <?php
7701          /* translators: Hidden accessibility text. */
7702          _e( 'Close dialog' );
7703          ?>
7704      </span></button>
7705      <?php
7706  
7707      if ( $same_domain ) {
7708          $login_src = add_query_arg(
7709              array(
7710                  'interim-login' => '1',
7711                  'wp_lang'       => get_user_locale(),
7712              ),
7713              $login_url
7714          );
7715          ?>
7716          <div id="wp-auth-check-form" class="loading" data-src="<?php echo esc_url( $login_src ); ?>"></div>
7717          <?php
7718      }
7719  
7720      ?>
7721      <div class="wp-auth-fallback">
7722          <p><b class="wp-auth-fallback-expired" tabindex="0"><?php _e( 'Session expired' ); ?></b></p>
7723          <p><a href="<?php echo esc_url( $login_url ); ?>" target="_blank"><?php _e( 'Please log in again.' ); ?></a>
7724          <?php _e( 'The login page will open in a new tab. After logging in you can close it and return to this page.' ); ?></p>
7725      </div>
7726      </div>
7727      </div>
7728      <?php
7729  }
7730  
7731  /**
7732   * Checks whether a user is still logged in, for the heartbeat.
7733   *
7734   * Send a result that shows a log-in box if the user is no longer logged in,
7735   * or if their cookie is within the grace period.
7736   *
7737   * @since 3.6.0
7738   *
7739   * @global int $login_grace_period
7740   *
7741   * @param array $response  The Heartbeat response.
7742   * @return array The Heartbeat response with 'wp-auth-check' value set.
7743   */
7744  function wp_auth_check( $response ) {
7745      $response['wp-auth-check'] = is_user_logged_in() && empty( $GLOBALS['login_grace_period'] );
7746      return $response;
7747  }
7748  
7749  /**
7750   * Returns RegEx body to liberally match an opening HTML tag.
7751   *
7752   * Matches an opening HTML tag that:
7753   * 1. Is self-closing or
7754   * 2. Has no body but has a closing tag of the same name or
7755   * 3. Contains a body and a closing tag of the same name
7756   *
7757   * Note: this RegEx does not balance inner tags and does not attempt
7758   * to produce valid HTML
7759   *
7760   * @since 3.6.0
7761   *
7762   * @param string $tag An HTML tag name. Example: 'video'.
7763   * @return string Tag RegEx.
7764   *
7765   * @phpstan-return ( $tag is ''|'0' ? '' : non-falsy-string )
7766   */
7767  function get_tag_regex( $tag ) {
7768      if ( empty( $tag ) ) {
7769          return '';
7770      }
7771      return sprintf( '<%1$s[^<]*?(?:>[\s\S]*?<\/%1$s>|\s*\/>)', tag_escape( $tag ) );
7772  }
7773  
7774  /**
7775   * Indicates if a given slug for a character set represents the UTF-8
7776   * text encoding. If not provided, examines the current blog's charset.
7777   *
7778   * A charset is considered to represent UTF-8 if it is a case-insensitive
7779   * match of "UTF-8" with or without the hyphen.
7780   *
7781   * Example:
7782   *
7783   *     true  === is_utf8_charset( 'UTF-8' );
7784   *     true  === is_utf8_charset( 'utf8' );
7785   *     false === is_utf8_charset( 'latin1' );
7786   *     false === is_utf8_charset( 'UTF 8' );
7787   *
7788   *     // Only strings match.
7789   *     false === is_utf8_charset( [ 'charset' => 'utf-8' ] );
7790   *
7791   *     // Without a given charset, it depends on the site option "blog_charset".
7792   *     $is_utf8 = is_utf8_charset();
7793   *
7794   * @since 6.6.0
7795   * @since 6.6.1 A wrapper for _is_utf8_charset
7796   *
7797   * @see _is_utf8_charset
7798   *
7799   * @param string|null $blog_charset Optional. Slug representing a text character encoding, or "charset".
7800   *                                  E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS".
7801   *                                  Default value is to infer from "blog_charset" option.
7802   * @return bool Whether the slug represents the UTF-8 encoding.
7803   */
7804  function is_utf8_charset( $blog_charset = null ) {
7805      return _is_utf8_charset( $blog_charset ?? get_option( 'blog_charset' ) );
7806  }
7807  
7808  /**
7809   * Retrieves a canonical form of the provided charset appropriate for passing to PHP
7810   * functions such as htmlspecialchars() and charset HTML attributes.
7811   *
7812   * @since 3.6.0
7813   * @access private
7814   *
7815   * @see https://core.trac.wordpress.org/ticket/23688
7816   *
7817   * @param string $charset A charset name, e.g. "UTF-8", "Windows-1252", "SJIS".
7818   * @return string The canonical form of the charset.
7819   */
7820  function _canonical_charset( $charset ) {
7821      if ( is_utf8_charset( $charset ) ) {
7822          return 'UTF-8';
7823      }
7824  
7825      /*
7826       * Normalize the ISO-8859-1 family of languages.
7827       *
7828       * This is not required for htmlspecialchars(), as it properly recognizes all of
7829       * the input character sets that here are transformed into "ISO-8859-1".
7830       *
7831       * @todo Should this entire check be removed since it's not required for the stated purpose?
7832       * @todo Should WordPress transform other potential charset equivalents, such as "latin1"?
7833       */
7834      if (
7835          ( 0 === strcasecmp( 'iso-8859-1', $charset ) ) ||
7836          ( 0 === strcasecmp( 'iso8859-1', $charset ) )
7837      ) {
7838          return 'ISO-8859-1';
7839      }
7840  
7841      return $charset;
7842  }
7843  
7844  /**
7845   * Sets the mbstring internal encoding to a binary safe encoding when func_overload
7846   * is enabled.
7847   *
7848   * When mbstring.func_overload is in use for multi-byte encodings, the results from
7849   * strlen() and similar functions respect the utf8 characters, causing binary data
7850   * to return incorrect lengths.
7851   *
7852   * This function overrides the mbstring encoding to a binary-safe encoding, and
7853   * resets it to the users expected encoding afterwards through the
7854   * `reset_mbstring_encoding` function.
7855   *
7856   * It is safe to recursively call this function, however each
7857   * `mbstring_binary_safe_encoding()` call must be followed up with an equal number
7858   * of `reset_mbstring_encoding()` calls.
7859   *
7860   * @since 3.7.0
7861   *
7862   * @see reset_mbstring_encoding()
7863   *
7864   * @param bool $reset Optional. Whether to reset the encoding back to a previously-set encoding.
7865   *                    Default false.
7866   */
7867  function mbstring_binary_safe_encoding( $reset = false ) {
7868      static $encodings  = array();
7869      static $overloaded = null;
7870  
7871      if ( is_null( $overloaded ) ) {
7872          if ( function_exists( 'mb_internal_encoding' )
7873              && ( (int) ini_get( 'mbstring.func_overload' ) & 2 ) // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated
7874          ) {
7875              $overloaded = true;
7876          } else {
7877              $overloaded = false;
7878          }
7879      }
7880  
7881      if ( false === $overloaded ) {
7882          return;
7883      }
7884  
7885      if ( ! $reset ) {
7886          $encoding = mb_internal_encoding();
7887          array_push( $encodings, $encoding );
7888          mb_internal_encoding( 'ISO-8859-1' );
7889      }
7890  
7891      if ( $reset && $encodings ) {
7892          $encoding = array_pop( $encodings );
7893          mb_internal_encoding( $encoding );
7894      }
7895  }
7896  
7897  /**
7898   * Resets the mbstring internal encoding to a users previously set encoding.
7899   *
7900   * @see mbstring_binary_safe_encoding()
7901   *
7902   * @since 3.7.0
7903   */
7904  function reset_mbstring_encoding() {
7905      mbstring_binary_safe_encoding( true );
7906  }
7907  
7908  /**
7909   * Filters/validates a variable as a boolean.
7910   *
7911   * Alternative to `filter_var( $value, FILTER_VALIDATE_BOOLEAN )`.
7912   *
7913   * @since 4.0.0
7914   *
7915   * @param mixed $value Boolean value to validate.
7916   * @return bool Whether the value is validated.
7917   */
7918  function wp_validate_boolean( $value ) {
7919      if ( is_bool( $value ) ) {
7920          return $value;
7921      }
7922  
7923      if ( is_string( $value ) && 'false' === strtolower( $value ) ) {
7924          return false;
7925      }
7926  
7927      return (bool) $value;
7928  }
7929  
7930  /**
7931   * Deletes a file.
7932   *
7933   * @since 4.2.0
7934   * @since 6.7.0 A return value was added.
7935   *
7936   * @param string $file The path to the file to delete.
7937   * @return bool True on success, false on failure.
7938   */
7939  function wp_delete_file( $file ) {
7940      /**
7941       * Filters the path of the file to delete.
7942       *
7943       * @since 2.1.0
7944       *
7945       * @param string $file Path to the file to delete.
7946       */
7947      $delete = apply_filters( 'wp_delete_file', $file );
7948  
7949      if ( ! empty( $delete ) ) {
7950          return @unlink( $delete );
7951      }
7952  
7953      return false;
7954  }
7955  
7956  /**
7957   * Deletes a file if its path is within the given directory.
7958   *
7959   * @since 4.9.7
7960   *
7961   * @param string $file      Absolute path to the file to delete.
7962   * @param string $directory Absolute path to a directory.
7963   * @return bool True on success, false on failure.
7964   */
7965  function wp_delete_file_from_directory( $file, $directory ) {
7966      if ( wp_is_stream( $file ) ) {
7967          $real_file      = $file;
7968          $real_directory = $directory;
7969      } else {
7970          $real_file      = realpath( wp_normalize_path( $file ) );
7971          $real_directory = realpath( wp_normalize_path( $directory ) );
7972      }
7973  
7974      if ( false !== $real_file ) {
7975          $real_file = wp_normalize_path( $real_file );
7976      }
7977  
7978      if ( false !== $real_directory ) {
7979          $real_directory = wp_normalize_path( $real_directory );
7980      }
7981  
7982      if ( false === $real_file || false === $real_directory || ! str_starts_with( $real_file, trailingslashit( $real_directory ) ) ) {
7983          return false;
7984      }
7985  
7986      return wp_delete_file( $file );
7987  }
7988  
7989  /**
7990   * Outputs a small JS snippet on preview tabs/windows to remove `window.name` when a user is navigating to another page.
7991   *
7992   * This prevents reusing the same tab for a preview when the user has navigated away.
7993   *
7994   * @since 4.3.0
7995   *
7996   * @global WP_Post $post Global post object.
7997   */
7998  function wp_post_preview_js() {
7999      global $post;
8000  
8001      if ( ! is_preview() || empty( $post ) ) {
8002          return;
8003      }
8004  
8005      // Has to match the window name used in post_submit_meta_box().
8006      $name = 'wp-preview-' . (int) $post->ID;
8007  
8008      ob_start();
8009      ?>
8010      <script>
8011      ( function() {
8012          var query = document.location.search;
8013  
8014          if ( query && query.indexOf( 'preview=true' ) !== -1 ) {
8015              window.name = '<?php echo $name; ?>';
8016          }
8017  
8018          if ( window.addEventListener ) {
8019              window.addEventListener( 'pagehide', function() { window.name = ''; } );
8020          }
8021      }());
8022      //# sourceURL=<?php echo rawurlencode( __FUNCTION__ ); ?>
8023      </script>
8024      <?php
8025      wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
8026  }
8027  
8028  /**
8029   * Parses and formats a MySQL datetime (Y-m-d H:i:s) for ISO8601 (Y-m-d\TH:i:s).
8030   *
8031   * Explicitly strips timezones, as datetimes are not saved with any timezone
8032   * information. Including any information on the offset could be misleading.
8033   *
8034   * Despite historical function name, the output does not conform to RFC3339 format,
8035   * which must contain timezone.
8036   *
8037   * @since 4.4.0
8038   *
8039   * @param string $date_string Date string to parse and format.
8040   * @return string Date formatted for ISO8601 without time zone.
8041   */
8042  function mysql_to_rfc3339( $date_string ) {
8043      return mysql2date( 'Y-m-d\TH:i:s', $date_string, false );
8044  }
8045  
8046  /**
8047   * Attempts to raise the PHP memory limit for memory intensive processes.
8048   *
8049   * Only allows raising the existing limit and prevents lowering it.
8050   *
8051   * @since 4.6.0
8052   *
8053   * @param string $context Optional. Context in which the function is called. Accepts either 'admin',
8054   *                        'image', 'cron', or an arbitrary other context. If an arbitrary context is passed,
8055   *                        the similarly arbitrary {@see '$context_memory_limit'} filter will be
8056   *                        invoked. Default 'admin'.
8057   * @return int|string|false The limit that was set or false on failure.
8058   */
8059  function wp_raise_memory_limit( $context = 'admin' ) {
8060      // Exit early if the limit cannot be changed.
8061      if ( false === wp_is_ini_value_changeable( 'memory_limit' ) ) {
8062          return false;
8063      }
8064  
8065      $current_limit     = ini_get( 'memory_limit' );
8066      $current_limit_int = wp_convert_hr_to_bytes( $current_limit );
8067  
8068      if ( -1 === $current_limit_int ) {
8069          return false;
8070      }
8071  
8072      $wp_max_limit     = WP_MAX_MEMORY_LIMIT;
8073      $wp_max_limit_int = wp_convert_hr_to_bytes( $wp_max_limit );
8074      $filtered_limit   = $wp_max_limit;
8075  
8076      switch ( $context ) {
8077          case 'admin':
8078              /**
8079               * Filters the maximum memory limit available for administration screens.
8080               *
8081               * This only applies to administrators, who may require more memory for tasks
8082               * like updates. Memory limits when processing images (uploaded or edited by
8083               * users of any role) are handled separately.
8084               *
8085               * The `WP_MAX_MEMORY_LIMIT` constant specifically defines the maximum memory
8086               * limit available when in the administration back end. The default is 256M
8087               * (256 megabytes of memory) or the original `memory_limit` php.ini value if
8088               * this is higher.
8089               *
8090               * @since 3.0.0
8091               * @since 4.6.0 The default now takes the original `memory_limit` into account.
8092               *
8093               * @param int|string $filtered_limit The maximum WordPress memory limit. Accepts an integer
8094               *                                   (bytes), or a shorthand string notation, such as '256M'.
8095               */
8096              $filtered_limit = apply_filters( 'admin_memory_limit', $filtered_limit );
8097              break;
8098  
8099          case 'image':
8100              /**
8101               * Filters the memory limit allocated for image manipulation.
8102               *
8103               * @since 3.5.0
8104               * @since 4.6.0 The default now takes the original `memory_limit` into account.
8105               *
8106               * @param int|string $filtered_limit Maximum memory limit to allocate for image processing.
8107               *                                   Default `WP_MAX_MEMORY_LIMIT` or the original
8108               *                                   php.ini `memory_limit`, whichever is higher.
8109               *                                   Accepts an integer (bytes), or a shorthand string
8110               *                                   notation, such as '256M'.
8111               */
8112              $filtered_limit = apply_filters( 'image_memory_limit', $filtered_limit );
8113              break;
8114  
8115          case 'cron':
8116              /**
8117               * Filters the memory limit allocated for WP-Cron event processing.
8118               *
8119               * @since 6.3.0
8120               *
8121               * @param int|string $filtered_limit Maximum memory limit to allocate for WP-Cron.
8122               *                                   Default `WP_MAX_MEMORY_LIMIT` or the original
8123               *                                   php.ini `memory_limit`, whichever is higher.
8124               *                                   Accepts an integer (bytes), or a shorthand string
8125               *                                   notation, such as '256M'.
8126               */
8127              $filtered_limit = apply_filters( 'cron_memory_limit', $filtered_limit );
8128              break;
8129  
8130          default:
8131              /**
8132               * Filters the memory limit allocated for an arbitrary context.
8133               *
8134               * The dynamic portion of the hook name, `$context`, refers to an arbitrary
8135               * context passed on calling the function. This allows for plugins to define
8136               * their own contexts for raising the memory limit.
8137               *
8138               * @since 4.6.0
8139               *
8140               * @param int|string $filtered_limit Maximum memory limit to allocate for this context.
8141               *                                   Default WP_MAX_MEMORY_LIMIT` or the original php.ini `memory_limit`,
8142               *                                   whichever is higher. Accepts an integer (bytes), or a
8143               *                                   shorthand string notation, such as '256M'.
8144               */
8145              $filtered_limit = apply_filters( "{$context}_memory_limit", $filtered_limit );
8146              break;
8147      }
8148  
8149      $filtered_limit_int = wp_convert_hr_to_bytes( $filtered_limit );
8150  
8151      if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) {
8152          if ( false !== ini_set( 'memory_limit', $filtered_limit ) ) {
8153              return $filtered_limit;
8154          } else {
8155              return false;
8156          }
8157      } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) {
8158          if ( false !== ini_set( 'memory_limit', $wp_max_limit ) ) {
8159              return $wp_max_limit;
8160          } else {
8161              return false;
8162          }
8163      }
8164  
8165      return false;
8166  }
8167  
8168  /**
8169   * Generates a random UUID (version 4).
8170   *
8171   * @since 4.7.0
8172   * @since 7.0.0 Uses wp_rand if available.
8173   *
8174   * @return string UUID.
8175   */
8176  function wp_generate_uuid4() {
8177      static $backup_randomizer = false;
8178      $randomizer               = function_exists( 'wp_rand' ) ? 'wp_rand' : $backup_randomizer;
8179  
8180      if ( false === $randomizer ) {
8181          try {
8182              random_int( 0, 15705 );
8183              $backup_randomizer = 'random_int';
8184          } catch ( Exception $e ) {
8185              $backup_randomizer = 'mt_rand';
8186          }
8187          $randomizer = $backup_randomizer;
8188      }
8189  
8190      return sprintf(
8191          '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
8192          $randomizer( 0, 0xffff ),
8193          $randomizer( 0, 0xffff ),
8194          $randomizer( 0, 0xffff ),
8195          $randomizer( 0, 0x0fff ) | 0x4000,
8196          $randomizer( 0, 0x3fff ) | 0x8000,
8197          $randomizer( 0, 0xffff ),
8198          $randomizer( 0, 0xffff ),
8199          $randomizer( 0, 0xffff )
8200      );
8201  }
8202  
8203  /**
8204   * Validates that a UUID is valid.
8205   *
8206   * @since 4.9.0
8207   *
8208   * @param mixed $uuid    UUID to check.
8209   * @param int   $version Specify which version of UUID to check against. Default is none,
8210   *                       to accept any UUID version. Otherwise, only version allowed is `4`.
8211   * @return bool The string is a valid UUID or false on failure.
8212   *
8213   * @phpstan-return ( $version is 4|null ? bool : false )
8214   */
8215  function wp_is_uuid( $uuid, $version = null ) {
8216  
8217      if ( ! is_string( $uuid ) ) {
8218          return false;
8219      }
8220  
8221      if ( is_numeric( $version ) ) {
8222          if ( 4 !== (int) $version ) {
8223              _doing_it_wrong( __FUNCTION__, __( 'Only UUID V4 is supported at this time.' ), '4.9.0' );
8224              return false;
8225          }
8226          $regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/';
8227      } else {
8228          $regex = '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/';
8229      }
8230  
8231      return (bool) preg_match( $regex, $uuid );
8232  }
8233  
8234  /**
8235   * Gets unique ID.
8236   *
8237   * This is a PHP implementation of Underscore's uniqueId method. A static variable
8238   * contains an integer that is incremented with each call. This number is returned
8239   * with the optional prefix. As such the returned value is not universally unique,
8240   * but it is unique across the life of the PHP process.
8241   *
8242   * @since 5.0.3
8243   *
8244   * @param string $prefix Prefix for the returned ID.
8245   * @return string Unique ID.
8246   *
8247   * @phpstan-return (
8248   *     ( $prefix is ''|numeric-string ? numeric-string : string )
8249   *     & non-falsy-string
8250   *     & ( $prefix is lowercase-string ? lowercase-string : string )
8251   * )
8252   */
8253  function wp_unique_id( $prefix = '' ) {
8254      static $id_counter = 0;
8255      return $prefix . (string) ++$id_counter;
8256  }
8257  
8258  /**
8259   * Generates an incremental ID that is independent per each different prefix.
8260   *
8261   * It is similar to `wp_unique_id`, but each prefix has its own internal ID
8262   * counter to make each prefix independent from each other. The ID starts at 1
8263   * and increments on each call. The returned value is not universally unique,
8264   * but it is unique across the life of the PHP process and it's stable per
8265   * prefix.
8266   *
8267   * @since 6.4.0
8268   *
8269   * @param string $prefix Optional. Prefix for the returned ID. Default empty string.
8270   * @return string Incremental ID per prefix.
8271   *
8272   * @phpstan-return (
8273   *     ( $prefix is ''|numeric-string ? numeric-string : string )
8274   *     & non-falsy-string
8275   *     & ( $prefix is lowercase-string ? lowercase-string : string )
8276   * )
8277   */
8278  function wp_unique_prefixed_id( $prefix = '' ) {
8279      static $id_counters = array();
8280  
8281      if ( ! is_string( $prefix ) ) {
8282          wp_trigger_error(
8283              __FUNCTION__,
8284              sprintf( 'The prefix must be a string. "%s" data type given.', gettype( $prefix ) )
8285          );
8286          $prefix = '';
8287      }
8288  
8289      if ( ! isset( $id_counters[ $prefix ] ) ) {
8290          $id_counters[ $prefix ] = 0;
8291      }
8292  
8293      $id = ++$id_counters[ $prefix ];
8294  
8295      return $prefix . (string) $id;
8296  }
8297  
8298  /**
8299   * Generates a unique ID based on the structure and values of a given array.
8300   *
8301   * This function serializes the array into a JSON string and generates a hash
8302   * that serves as a unique identifier. Optionally, a prefix can be added to
8303   * the generated ID for context or categorization.
8304   *
8305   * @since 6.8.0
8306   *
8307   * @param array  $data   The input array to generate an ID from.
8308   * @param string $prefix Optional. A prefix to prepend to the generated ID. Default empty string.
8309   * @return string The generated unique ID for the array.
8310   *
8311   * @phpstan-return ( $prefix is lowercase-string ? lowercase-string&non-falsy-string : non-falsy-string )
8312   */
8313  function wp_unique_id_from_values( array $data, string $prefix = '' ): string {
8314      if ( empty( $data ) ) {
8315          _doing_it_wrong(
8316              __FUNCTION__,
8317              sprintf(
8318                  /* translators: %s: The parameter name. */
8319                  __( 'The %s parameter must not be empty.' ),
8320                  '$data'
8321              ),
8322              '6.8.0'
8323          );
8324      }
8325  
8326      $serialized = wp_json_encode( $data );
8327      $hash       = substr( md5( $serialized ), 0, 8 );
8328  
8329      return $prefix . $hash;
8330  }
8331  
8332  /**
8333   * Gets last changed date for the specified cache group.
8334   *
8335   * @since 4.7.0
8336   *
8337   * @param string $group Where the cache contents are grouped.
8338   * @return string UNIX timestamp with microseconds representing when the group was last changed.
8339   */
8340  function wp_cache_get_last_changed( $group ) {
8341      $last_changed = wp_cache_get( 'last_changed', $group );
8342  
8343      if ( $last_changed ) {
8344          return $last_changed;
8345      }
8346  
8347      return wp_cache_set_last_changed( $group );
8348  }
8349  
8350  /**
8351   * Sets last changed date for the specified cache group to now.
8352   *
8353   * @since 6.3.0
8354   *
8355   * @param string $group Where the cache contents are grouped.
8356   * @return string UNIX timestamp when the group was last changed.
8357   */
8358  function wp_cache_set_last_changed( $group ) {
8359      $previous_time = wp_cache_get( 'last_changed', $group );
8360  
8361      $time = microtime();
8362  
8363      wp_cache_set( 'last_changed', $time, $group );
8364  
8365      /**
8366       * Fires after a cache group `last_changed` time is updated.
8367       * This may occur multiple times per page load and registered
8368       * actions must be performant.
8369       *
8370       * @since 6.3.0
8371       *
8372       * @param string       $group         The cache group name.
8373       * @param string       $time          The new last changed time (msec sec).
8374       * @param string|false $previous_time The previous last changed time. False if not previously set.
8375       */
8376      do_action( 'wp_cache_set_last_changed', $group, $time, $previous_time );
8377  
8378      return $time;
8379  }
8380  
8381  /**
8382   * Sends an email to the old site admin email address when the site admin email address changes.
8383   *
8384   * @since 4.9.0
8385   *
8386   * @param string $old_email   The old site admin email address.
8387   * @param string $new_email   The new site admin email address.
8388   * @param string $option_name The relevant database option name.
8389   */
8390  function wp_site_admin_email_change_notification( $old_email, $new_email, $option_name ) {
8391      $send = true;
8392  
8393      // Don't send the notification for an empty email address or the default 'admin_email' value.
8394      if ( empty( $old_email ) || 'you@example.com' === $old_email ) {
8395          $send = false;
8396      }
8397  
8398      /**
8399       * Filters whether to send the site admin email change notification email.
8400       *
8401       * @since 4.9.0
8402       *
8403       * @param bool   $send      Whether to send the email notification.
8404       * @param string $old_email The old site admin email address.
8405       * @param string $new_email The new site admin email address.
8406       */
8407      $send = apply_filters( 'send_site_admin_email_change_email', $send, $old_email, $new_email );
8408  
8409      if ( ! $send ) {
8410          return;
8411      }
8412  
8413      /* translators: Do not translate OLD_EMAIL, NEW_EMAIL, SITENAME, SITEURL: those are placeholders. */
8414      $email_change_text = __(
8415          'Hi,
8416  
8417  This notice confirms that the admin email address was changed on ###SITENAME###.
8418  
8419  The new admin email address is ###NEW_EMAIL###.
8420  
8421  This email has been sent to ###OLD_EMAIL###
8422  
8423  Regards,
8424  All at ###SITENAME###
8425  ###SITEURL###'
8426      );
8427  
8428      $email_change_email = array(
8429          'to'      => $old_email,
8430          /* translators: Site admin email change notification email subject. %s: Site title. */
8431          'subject' => __( '[%s] Admin Email Changed' ),
8432          'message' => $email_change_text,
8433          'headers' => '',
8434      );
8435  
8436      // Get site name.
8437      $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
8438  
8439      /**
8440       * Filters the contents of the email notification sent when the site admin email address is changed.
8441       *
8442       * @since 4.9.0
8443       *
8444       * @param array  $email_change_email {
8445       *     Used to build wp_mail().
8446       *
8447       *     @type string $to      The intended recipient.
8448       *     @type string $subject The subject of the email.
8449       *     @type string $message The content of the email.
8450       *         The following strings have a special meaning and will get replaced dynamically:
8451       *          - `###OLD_EMAIL###` The old site admin email address.
8452       *          - `###NEW_EMAIL###` The new site admin email address.
8453       *          - `###SITENAME###`  The name of the site.
8454       *          - `###SITEURL###`   The URL to the site.
8455       *     @type string $headers Headers.
8456       * }
8457       * @param string $old_email          The old site admin email address.
8458       * @param string $new_email          The new site admin email address.
8459       */
8460      $email_change_email = apply_filters( 'site_admin_email_change_email', $email_change_email, $old_email, $new_email );
8461  
8462      $email_change_email['message'] = str_replace( '###OLD_EMAIL###', $old_email, $email_change_email['message'] );
8463      $email_change_email['message'] = str_replace( '###NEW_EMAIL###', $new_email, $email_change_email['message'] );
8464      $email_change_email['message'] = str_replace( '###SITENAME###', $site_name, $email_change_email['message'] );
8465      $email_change_email['message'] = str_replace( '###SITEURL###', home_url(), $email_change_email['message'] );
8466  
8467      wp_mail(
8468          $email_change_email['to'],
8469          sprintf(
8470              $email_change_email['subject'],
8471              $site_name
8472          ),
8473          $email_change_email['message'],
8474          $email_change_email['headers']
8475      );
8476  }
8477  
8478  /**
8479   * Returns an anonymized IPv4 or IPv6 address.
8480   *
8481   * @since 4.9.6 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`.
8482   *
8483   * @param string $ip_addr       The IPv4 or IPv6 address to be anonymized.
8484   * @param bool   $ipv6_fallback Optional. Whether to return the original IPv6 address if the needed functions
8485   *                              to anonymize it are not present. Default false, return `::` (unspecified address).
8486   * @return string  The anonymized IP address.
8487   */
8488  function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) {
8489      if ( empty( $ip_addr ) ) {
8490          return '0.0.0.0';
8491      }
8492  
8493      // Detect what kind of IP address this is.
8494      $ip_prefix = '';
8495      $is_ipv6   = substr_count( $ip_addr, ':' ) > 1;
8496      $is_ipv4   = ( 3 === substr_count( $ip_addr, '.' ) );
8497  
8498      if ( $is_ipv6 && $is_ipv4 ) {
8499          // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4.
8500          $ip_prefix = '::ffff:';
8501          $ip_addr   = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr );
8502          $ip_addr   = str_replace( ']', '', $ip_addr );
8503          $is_ipv6   = false;
8504      }
8505  
8506      if ( $is_ipv6 ) {
8507          // IPv6 addresses will always be enclosed in [] if there's a port.
8508          $left_bracket  = strpos( $ip_addr, '[' );
8509          $right_bracket = strpos( $ip_addr, ']' );
8510          $percent       = strpos( $ip_addr, '%' );
8511          $netmask       = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
8512  
8513          // Strip the port (and [] from IPv6 addresses), if they exist.
8514          if ( false !== $left_bracket && false !== $right_bracket ) {
8515              $ip_addr = substr( $ip_addr, $left_bracket + 1, $right_bracket - $left_bracket - 1 );
8516          } elseif ( false !== $left_bracket || false !== $right_bracket ) {
8517              // The IP has one bracket, but not both, so it's malformed.
8518              return '::';
8519          }
8520  
8521          // Strip the reachability scope.
8522          if ( false !== $percent ) {
8523              $ip_addr = substr( $ip_addr, 0, $percent );
8524          }
8525  
8526          // No invalid characters should be left.
8527          if ( preg_match( '/[^0-9a-f:]/i', $ip_addr ) ) {
8528              return '::';
8529          }
8530  
8531          // Partially anonymize the IP by reducing it to the corresponding network ID.
8532          if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
8533              $ip_addr = inet_ntop( inet_pton( $ip_addr ) & inet_pton( $netmask ) );
8534              if ( false === $ip_addr ) {
8535                  return '::';
8536              }
8537          } elseif ( ! $ipv6_fallback ) {
8538              return '::';
8539          }
8540      } elseif ( $is_ipv4 ) {
8541          // Strip any port and partially anonymize the IP.
8542          $last_octet_position = strrpos( $ip_addr, '.' );
8543          $ip_addr             = substr( $ip_addr, 0, $last_octet_position ) . '.0';
8544      } else {
8545          return '0.0.0.0';
8546      }
8547  
8548      // Restore the IPv6 prefix to compatibility mode addresses.
8549      return $ip_prefix . $ip_addr;
8550  }
8551  
8552  /**
8553   * Returns uniform "anonymous" data by type.
8554   *
8555   * @since 4.9.6
8556   *
8557   * @param string $type The type of data to be anonymized.
8558   * @param string $data Optional. The data to be anonymized. Default empty string.
8559   * @return string The anonymous data for the requested type.
8560   */
8561  function wp_privacy_anonymize_data( $type, $data = '' ) {
8562  
8563      switch ( $type ) {
8564          case 'email':
8565              $anonymous = 'deleted@site.invalid';
8566              break;
8567          case 'url':
8568              $anonymous = 'https://site.invalid';
8569              break;
8570          case 'ip':
8571              $anonymous = wp_privacy_anonymize_ip( $data );
8572              break;
8573          case 'date':
8574              $anonymous = '0000-00-00 00:00:00';
8575              break;
8576          case 'text':
8577              /* translators: Deleted text. */
8578              $anonymous = __( '[deleted]' );
8579              break;
8580          case 'longtext':
8581              /* translators: Deleted long text. */
8582              $anonymous = __( 'This content was deleted by the author.' );
8583              break;
8584          default:
8585              $anonymous = '';
8586              break;
8587      }
8588  
8589      /**
8590       * Filters the anonymous data for each type.
8591       *
8592       * @since 4.9.6
8593       *
8594       * @param string $anonymous Anonymized data.
8595       * @param string $type      Type of the data.
8596       * @param string $data      Original data.
8597       */
8598      return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data );
8599  }
8600  
8601  /**
8602   * Returns the directory used to store personal data export files.
8603   *
8604   * @since 4.9.6
8605   *
8606   * @see wp_privacy_exports_url
8607   *
8608   * @return string Exports directory.
8609   */
8610  function wp_privacy_exports_dir() {
8611      $upload_dir  = wp_upload_dir();
8612      $exports_dir = trailingslashit( $upload_dir['basedir'] ) . 'wp-personal-data-exports/';
8613  
8614      /**
8615       * Filters the directory used to store personal data export files.
8616       *
8617       * @since 4.9.6
8618       * @since 5.5.0 Exports now use relative paths, so changes to the directory
8619       *              via this filter should be reflected on the server.
8620       *
8621       * @param string $exports_dir Exports directory.
8622       */
8623      return apply_filters( 'wp_privacy_exports_dir', $exports_dir );
8624  }
8625  
8626  /**
8627   * Returns the URL of the directory used to store personal data export files.
8628   *
8629   * @since 4.9.6
8630   *
8631   * @see wp_privacy_exports_dir
8632   *
8633   * @return string Exports directory URL.
8634   */
8635  function wp_privacy_exports_url() {
8636      $upload_dir  = wp_upload_dir();
8637      $exports_url = trailingslashit( $upload_dir['baseurl'] ) . 'wp-personal-data-exports/';
8638  
8639      /**
8640       * Filters the URL of the directory used to store personal data export files.
8641       *
8642       * @since 4.9.6
8643       * @since 5.5.0 Exports now use relative paths, so changes to the directory URL
8644       *              via this filter should be reflected on the server.
8645       *
8646       * @param string $exports_url Exports directory URL.
8647       */
8648      return apply_filters( 'wp_privacy_exports_url', $exports_url );
8649  }
8650  
8651  /**
8652   * Schedules a `WP_Cron` job to delete expired export files.
8653   *
8654   * @since 4.9.6
8655   */
8656  function wp_schedule_delete_old_privacy_export_files() {
8657      if ( wp_installing() ) {
8658          return;
8659      }
8660  
8661      if ( ! wp_next_scheduled( 'wp_privacy_delete_old_export_files' ) ) {
8662          wp_schedule_event( time(), 'hourly', 'wp_privacy_delete_old_export_files' );
8663      }
8664  }
8665  
8666  /**
8667   * Schedules a WP-Cron job to clean up personal data requests.
8668   *
8669   * @since 7.1.0
8670   *
8671   * @see wp_privacy_personal_data_cleanup_requests()
8672   */
8673  function wp_schedule_personal_data_cleanup_requests(): void {
8674      if ( wp_installing() ) {
8675          return;
8676      }
8677  
8678      if ( ! wp_next_scheduled( 'wp_privacy_personal_data_cleanup_requests' ) ) {
8679          wp_schedule_event( time(), 'daily', 'wp_privacy_personal_data_cleanup_requests' );
8680      }
8681  }
8682  
8683  /**
8684   * Fires the personal data cleanup requests handler during cron.
8685   *
8686   * Loads the admin privacy tools file if needed (e.g. during cron, where
8687   * wp-admin/includes/privacy-tools.php is not loaded automatically).
8688   *
8689   * @since 7.1.0
8690   */
8691  function wp_privacy_personal_data_cleanup_requests(): void {
8692      if ( ! function_exists( '_wp_personal_data_cleanup_requests' ) ) {
8693          require_once  ABSPATH . 'wp-admin/includes/privacy-tools.php';
8694      }
8695      _wp_personal_data_cleanup_requests();
8696  }
8697  
8698  /**
8699   * Cleans up export files older than three days old.
8700   *
8701   * The export files are stored in `wp-content/uploads`, and are therefore publicly
8702   * accessible. A CSPRN is appended to the filename to mitigate the risk of an
8703   * unauthorized person downloading the file, but it is still possible. Deleting
8704   * the file after the data subject has had a chance to delete it adds an additional
8705   * layer of protection.
8706   *
8707   * @since 4.9.6
8708   */
8709  function wp_privacy_delete_old_export_files() {
8710      $exports_dir = wp_privacy_exports_dir();
8711      if ( ! is_dir( $exports_dir ) ) {
8712          return;
8713      }
8714  
8715      require_once  ABSPATH . 'wp-admin/includes/file.php';
8716      $export_files = list_files( $exports_dir, 100, array( 'index.php' ) );
8717  
8718      /**
8719       * Filters the lifetime, in seconds, of a personal data export file.
8720       *
8721       * By default, the lifetime is 3 days. Once the file reaches that age, it will automatically
8722       * be deleted by a cron job.
8723       *
8724       * @since 4.9.6
8725       *
8726       * @param int $expiration The expiration age of the export, in seconds.
8727       */
8728      $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
8729  
8730      foreach ( (array) $export_files as $export_file ) {
8731          $file_age_in_seconds = time() - filemtime( $export_file );
8732  
8733          if ( $expiration < $file_age_in_seconds ) {
8734              unlink( $export_file );
8735          }
8736      }
8737  }
8738  
8739  /**
8740   * Gets the URL to learn more about updating the PHP version the site is running on.
8741   *
8742   * This URL can be overridden by specifying an environment variable `WP_UPDATE_PHP_URL` or by using the
8743   * {@see 'wp_update_php_url'} filter. Providing an empty string is not allowed and will result in the
8744   * default URL being used. Furthermore the page the URL links to should preferably be localized in the
8745   * site language.
8746   *
8747   * @since 5.1.0
8748   *
8749   * @return string URL to learn more about updating PHP.
8750   */
8751  function wp_get_update_php_url() {
8752      $default_url = wp_get_default_update_php_url();
8753  
8754      $update_url = $default_url;
8755      if ( false !== getenv( 'WP_UPDATE_PHP_URL' ) ) {
8756          $update_url = getenv( 'WP_UPDATE_PHP_URL' );
8757      }
8758  
8759      /**
8760       * Filters the URL to learn more about updating the PHP version the site is running on.
8761       *
8762       * Providing an empty string is not allowed and will result in the default URL being used. Furthermore
8763       * the page the URL links to should preferably be localized in the site language.
8764       *
8765       * @since 5.1.0
8766       *
8767       * @param string $update_url URL to learn more about updating PHP.
8768       */
8769      $update_url = apply_filters( 'wp_update_php_url', $update_url );
8770  
8771      if ( empty( $update_url ) ) {
8772          $update_url = $default_url;
8773      }
8774  
8775      return $update_url;
8776  }
8777  
8778  /**
8779   * Gets the default URL to learn more about updating the PHP version the site is running on.
8780   *
8781   * Do not use this function to retrieve this URL. Instead, use {@see wp_get_update_php_url()} when relying on the URL.
8782   * This function does not allow modifying the returned URL, and is only used to compare the actually used URL with the
8783   * default one.
8784   *
8785   * @since 5.1.0
8786   * @access private
8787   *
8788   * @return string Default URL to learn more about updating PHP.
8789   */
8790  function wp_get_default_update_php_url() {
8791      return _x( 'https://wordpress.org/support/update-php/', 'localized PHP upgrade information page' );
8792  }
8793  
8794  /**
8795   * Prints the default annotation for the web host altering the "Update PHP" page URL.
8796   *
8797   * This function is to be used after {@see wp_get_update_php_url()} to display a consistent
8798   * annotation if the web host has altered the default "Update PHP" page URL.
8799   *
8800   * @since 5.1.0
8801   * @since 5.2.0 Added the `$before` and `$after` parameters.
8802   * @since 6.4.0 Added the `$display` parameter.
8803   *
8804   * @param string $before  Markup to output before the annotation. Default `<p class="description">`.
8805   * @param string $after   Markup to output after the annotation. Default `</p>`.
8806   * @param bool   $display Whether to echo or return the markup. Default `true` for echo.
8807   * @return string|null|void Update PHP page annotation when `$display` is false, null when
8808   *                          no annotation is available. Nothing otherwise.
8809   * @phpstan-return ( $display is true ? void : string|null )
8810   */
8811  function wp_update_php_annotation( $before = '<p class="description">', $after = '</p>', $display = true ) {
8812      $annotation = wp_get_update_php_annotation();
8813  
8814      if ( ! $annotation ) {
8815          return null;
8816      }
8817  
8818      if ( ! $display ) {
8819          return $before . $annotation . $after;
8820      }
8821  
8822      echo $before . $annotation . $after;
8823  }
8824  
8825  /**
8826   * Returns the default annotation for the web hosting altering the "Update PHP" page URL.
8827   *
8828   * This function is to be used after {@see wp_get_update_php_url()} to return a consistent
8829   * annotation if the web host has altered the default "Update PHP" page URL.
8830   *
8831   * @since 5.2.0
8832   *
8833   * @return string Update PHP page annotation. An empty string if no custom URLs are provided.
8834   */
8835  function wp_get_update_php_annotation() {
8836      $update_url  = wp_get_update_php_url();
8837      $default_url = wp_get_default_update_php_url();
8838  
8839      if ( $update_url === $default_url ) {
8840          return '';
8841      }
8842  
8843      $annotation = sprintf(
8844          /* translators: %s: Default Update PHP page URL. */
8845          __( 'This resource is provided by your web host, and is specific to your site. For more information, <a href="%s" target="_blank">see the official WordPress documentation</a>.' ),
8846          esc_url( $default_url )
8847      );
8848  
8849      return $annotation;
8850  }
8851  
8852  /**
8853   * Gets the URL for directly updating the PHP version the site is running on.
8854   *
8855   * A URL will only be returned if the `WP_DIRECT_UPDATE_PHP_URL` environment variable is specified or
8856   * by using the {@see 'wp_direct_php_update_url'} filter. This allows hosts to send users directly to
8857   * the page where they can update PHP to a newer version.
8858   *
8859   * @since 5.1.1
8860   *
8861   * @return string URL for directly updating PHP or empty string.
8862   */
8863  function wp_get_direct_php_update_url() {
8864      $direct_update_url = '';
8865  
8866      if ( false !== getenv( 'WP_DIRECT_UPDATE_PHP_URL' ) ) {
8867          $direct_update_url = getenv( 'WP_DIRECT_UPDATE_PHP_URL' );
8868      }
8869  
8870      /**
8871       * Filters the URL for directly updating the PHP version the site is running on from the host.
8872       *
8873       * @since 5.1.1
8874       *
8875       * @param string $direct_update_url URL for directly updating PHP.
8876       */
8877      $direct_update_url = apply_filters( 'wp_direct_php_update_url', $direct_update_url );
8878  
8879      return $direct_update_url;
8880  }
8881  
8882  /**
8883   * Displays a button directly linking to a PHP update process.
8884   *
8885   * This provides hosts with a way for users to be sent directly to their PHP update process.
8886   *
8887   * The button is only displayed if a URL is returned by `wp_get_direct_php_update_url()`.
8888   *
8889   * @since 5.1.1
8890   */
8891  function wp_direct_php_update_button() {
8892      $direct_update_url = wp_get_direct_php_update_url();
8893  
8894      if ( empty( $direct_update_url ) ) {
8895          return;
8896      }
8897  
8898      echo '<p class="button-container">';
8899      printf(
8900          '<a class="button button-primary" href="%1$s" target="_blank">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
8901          esc_url( $direct_update_url ),
8902          __( 'Update PHP' ),
8903          /* translators: Hidden accessibility text. */
8904          __( '(opens in a new tab)' )
8905      );
8906      echo '</p>';
8907  }
8908  
8909  /**
8910   * Gets the URL to learn more about updating the site to use HTTPS.
8911   *
8912   * This URL can be overridden by specifying an environment variable `WP_UPDATE_HTTPS_URL` or by using the
8913   * {@see 'wp_update_https_url'} filter. Providing an empty string is not allowed and will result in the
8914   * default URL being used. Furthermore the page the URL links to should preferably be localized in the
8915   * site language.
8916   *
8917   * @since 5.7.0
8918   *
8919   * @return string URL to learn more about updating to HTTPS.
8920   */
8921  function wp_get_update_https_url() {
8922      $default_url = wp_get_default_update_https_url();
8923  
8924      $update_url = $default_url;
8925      if ( false !== getenv( 'WP_UPDATE_HTTPS_URL' ) ) {
8926          $update_url = getenv( 'WP_UPDATE_HTTPS_URL' );
8927      }
8928  
8929      /**
8930       * Filters the URL to learn more about updating the HTTPS version the site is running on.
8931       *
8932       * Providing an empty string is not allowed and will result in the default URL being used. Furthermore
8933       * the page the URL links to should preferably be localized in the site language.
8934       *
8935       * @since 5.7.0
8936       *
8937       * @param string $update_url URL to learn more about updating HTTPS.
8938       */
8939      $update_url = apply_filters( 'wp_update_https_url', $update_url );
8940      if ( empty( $update_url ) ) {
8941          $update_url = $default_url;
8942      }
8943  
8944      return $update_url;
8945  }
8946  
8947  /**
8948   * Gets the default URL to learn more about updating the site to use HTTPS.
8949   *
8950   * Do not use this function to retrieve this URL. Instead, use {@see wp_get_update_https_url()} when relying on the URL.
8951   * This function does not allow modifying the returned URL, and is only used to compare the actually used URL with the
8952   * default one.
8953   *
8954   * @since 5.7.0
8955   * @access private
8956   *
8957   * @return string Default URL to learn more about updating to HTTPS.
8958   */
8959  function wp_get_default_update_https_url() {
8960      /* translators: Documentation explaining HTTPS and why it should be used. */
8961      return __( 'https://developer.wordpress.org/advanced-administration/security/https/' );
8962  }
8963  
8964  /**
8965   * Gets the URL for directly updating the site to use HTTPS.
8966   *
8967   * A URL will only be returned if the `WP_DIRECT_UPDATE_HTTPS_URL` environment variable is specified or
8968   * by using the {@see 'wp_direct_update_https_url'} filter. This allows hosts to send users directly to
8969   * the page where they can update their site to use HTTPS.
8970   *
8971   * @since 5.7.0
8972   *
8973   * @return string URL for directly updating to HTTPS or empty string.
8974   */
8975  function wp_get_direct_update_https_url() {
8976      $direct_update_url = '';
8977  
8978      if ( false !== getenv( 'WP_DIRECT_UPDATE_HTTPS_URL' ) ) {
8979          $direct_update_url = getenv( 'WP_DIRECT_UPDATE_HTTPS_URL' );
8980      }
8981  
8982      /**
8983       * Filters the URL for directly updating the PHP version the site is running on from the host.
8984       *
8985       * @since 5.7.0
8986       *
8987       * @param string $direct_update_url URL for directly updating PHP.
8988       */
8989      $direct_update_url = apply_filters( 'wp_direct_update_https_url', $direct_update_url );
8990  
8991      return $direct_update_url;
8992  }
8993  
8994  /**
8995   * Gets the size of a directory.
8996   *
8997   * A helper function that is used primarily to check whether
8998   * a blog has exceeded its allowed upload space.
8999   *
9000   * @since MU (3.0.0)
9001   * @since 5.2.0 $max_execution_time parameter added.
9002   *
9003   * @param string $directory          Full path of a directory.
9004   * @param int    $max_execution_time Maximum time to run before giving up. In seconds.
9005   *                                   The timeout is global and is measured from the moment WordPress started to load.
9006   * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout.
9007   */
9008  function get_dirsize( $directory, $max_execution_time = null ) {
9009  
9010      /*
9011       * Exclude individual site directories from the total when checking the main site of a network,
9012       * as they are subdirectories and should not be counted.
9013       */
9014      if ( is_multisite() && is_main_site() ) {
9015          $size = recurse_dirsize( $directory, $directory . '/sites', $max_execution_time );
9016      } else {
9017          $size = recurse_dirsize( $directory, null, $max_execution_time );
9018      }
9019  
9020      return $size;
9021  }
9022  
9023  /**
9024   * Gets the size of a directory recursively.
9025   *
9026   * Used by get_dirsize() to get a directory size when it contains other directories.
9027   *
9028   * @since MU (3.0.0)
9029   * @since 4.3.0 The `$exclude` parameter was added.
9030   * @since 5.2.0 The `$max_execution_time` parameter was added.
9031   * @since 5.6.0 The `$directory_cache` parameter was added.
9032   *
9033   * @param string          $directory          Full path of a directory.
9034   * @param string|string[] $exclude            Optional. Full path of a subdirectory to exclude from the total,
9035   *                                            or array of paths. Expected without trailing slash(es).
9036   *                                            Default null.
9037   * @param int             $max_execution_time Optional. Maximum time to run before giving up. In seconds.
9038   *                                            The timeout is global and is measured from the moment
9039   *                                            WordPress started to load. Defaults to the value of
9040   *                                            `max_execution_time` PHP setting.
9041   * @param array           $directory_cache    Optional. Array of cached directory paths.
9042   *                                            Defaults to the value of `dirsize_cache` transient.
9043   * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout.
9044   */
9045  function recurse_dirsize( $directory, $exclude = null, $max_execution_time = null, &$directory_cache = null ) {
9046      $directory  = untrailingslashit( $directory );
9047      $save_cache = false;
9048  
9049      if ( ! isset( $directory_cache ) ) {
9050          $directory_cache = get_transient( 'dirsize_cache' );
9051          $save_cache      = true;
9052      }
9053  
9054      if ( isset( $directory_cache[ $directory ] ) && is_int( $directory_cache[ $directory ] ) ) {
9055          return $directory_cache[ $directory ];
9056      }
9057  
9058      if ( ! file_exists( $directory ) || ! is_dir( $directory ) || ! is_readable( $directory ) ) {
9059          return false;
9060      }
9061  
9062      if (
9063          ( is_string( $exclude ) && $directory === $exclude ) ||
9064          ( is_array( $exclude ) && in_array( $directory, $exclude, true ) )
9065      ) {
9066          return false;
9067      }
9068  
9069      if ( null === $max_execution_time ) {
9070          // Keep the previous behavior but attempt to prevent fatal errors from timeout if possible.
9071          $max_execution_time = ini_get( 'max_execution_time' );
9072  
9073          // Leave 1 second "buffer" for other operations if $max_execution_time has reasonable value.
9074          if ( $max_execution_time > 10 ) {
9075              $max_execution_time -= 1;
9076          }
9077      }
9078  
9079      /**
9080       * Filters the amount of storage space used by one directory and all its children, in megabytes.
9081       *
9082       * Return the actual used space to short-circuit the recursive PHP file size calculation
9083       * and use something else, like a CDN API or native operating system tools for better performance.
9084       *
9085       * @since 5.6.0
9086       *
9087       * @param int|false            $space_used         The amount of used space, in bytes. Default false.
9088       * @param string               $directory          Full path of a directory.
9089       * @param string|string[]|null $exclude            Full path of a subdirectory to exclude from the total,
9090       *                                                 or array of paths.
9091       * @param int                  $max_execution_time Maximum time to run before giving up. In seconds.
9092       * @param array                $directory_cache    Array of cached directory paths.
9093       */
9094      $size = apply_filters( 'pre_recurse_dirsize', false, $directory, $exclude, $max_execution_time, $directory_cache );
9095  
9096      if ( false === $size ) {
9097          $size = 0;
9098  
9099          $handle = opendir( $directory );
9100          if ( $handle ) {
9101              while ( ( $file = readdir( $handle ) ) !== false ) {
9102                  $path = $directory . '/' . $file;
9103                  if ( '.' !== $file && '..' !== $file ) {
9104                      if ( is_file( $path ) ) {
9105                          $size += filesize( $path );
9106                      } elseif ( is_dir( $path ) ) {
9107                          $handlesize = recurse_dirsize( $path, $exclude, $max_execution_time, $directory_cache );
9108                          if ( $handlesize > 0 ) {
9109                              $size += $handlesize;
9110                          }
9111                      }
9112  
9113                      if ( $max_execution_time > 0 &&
9114                          ( microtime( true ) - WP_START_TIMESTAMP ) > $max_execution_time
9115                      ) {
9116                          // Time exceeded. Give up instead of risking a fatal timeout.
9117                          $size = null;
9118                          break;
9119                      }
9120                  }
9121              }
9122              closedir( $handle );
9123          }
9124      }
9125  
9126      if ( ! is_array( $directory_cache ) ) {
9127          $directory_cache = array();
9128      }
9129  
9130      $directory_cache[ $directory ] = $size;
9131  
9132      // Only write the transient on the top level call and not on recursive calls.
9133      if ( $save_cache ) {
9134          $expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS;
9135          set_transient( 'dirsize_cache', $directory_cache, $expiration );
9136      }
9137  
9138      return $size;
9139  }
9140  
9141  /**
9142   * Cleans directory size cache used by recurse_dirsize().
9143   *
9144   * Removes the current directory and all parent directories from the `dirsize_cache` transient.
9145   *
9146   * @since 5.6.0
9147   * @since 5.9.0 Added input validation with a notice for invalid input.
9148   *
9149   * @param string $path Full path of a directory or file.
9150   */
9151  function clean_dirsize_cache( $path ) {
9152      if ( ! is_string( $path ) || empty( $path ) ) {
9153          wp_trigger_error(
9154              '',
9155              sprintf(
9156                  /* translators: 1: Function name, 2: A variable type, like "boolean" or "integer". */
9157                  __( '%1$s only accepts a non-empty path string, received %2$s.' ),
9158                  '<code>clean_dirsize_cache()</code>',
9159                  '<code>' . gettype( $path ) . '</code>'
9160              )
9161          );
9162          return;
9163      }
9164  
9165      $directory_cache = get_transient( 'dirsize_cache' );
9166  
9167      if ( empty( $directory_cache ) ) {
9168          return;
9169      }
9170  
9171      $expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS;
9172      if (
9173          ! str_contains( $path, '/' ) &&
9174          ! str_contains( $path, '\\' )
9175      ) {
9176          unset( $directory_cache[ $path ] );
9177          set_transient( 'dirsize_cache', $directory_cache, $expiration );
9178          return;
9179      }
9180  
9181      $last_path = null;
9182      $path      = untrailingslashit( $path );
9183      unset( $directory_cache[ $path ] );
9184  
9185      while (
9186          $last_path !== $path &&
9187          DIRECTORY_SEPARATOR !== $path &&
9188          '.' !== $path &&
9189          '..' !== $path
9190      ) {
9191          $last_path = $path;
9192          $path      = dirname( $path );
9193          unset( $directory_cache[ $path ] );
9194      }
9195  
9196      set_transient( 'dirsize_cache', $directory_cache, $expiration );
9197  }
9198  
9199  /**
9200   * Returns the current WordPress version.
9201   *
9202   * Returns an unmodified value of `$wp_version`. Some plugins modify the global
9203   * in an attempt to improve security through obscurity. This practice can cause
9204   * errors in WordPress, so the ability to get an unmodified version is needed.
9205   *
9206   * @since 6.7.0
9207   *
9208   * @return string The current WordPress version.
9209   */
9210  function wp_get_wp_version() {
9211      static $wp_version;
9212  
9213      if ( ! isset( $wp_version ) ) {
9214          require  ABSPATH . WPINC . '/version.php';
9215      }
9216  
9217      return $wp_version;
9218  }
9219  
9220  /**
9221   * Checks compatibility with the current WordPress version.
9222   *
9223   * @since 5.2.0
9224   *
9225   * @global string $_wp_tests_wp_version The WordPress version string. Used only in Core tests.
9226   *
9227   * @param string $required Minimum required WordPress version.
9228   * @return bool True if required version is compatible or empty, false if not.
9229   */
9230  function is_wp_version_compatible( $required ) {
9231      if (
9232          defined( 'WP_RUN_CORE_TESTS' )
9233          && WP_RUN_CORE_TESTS
9234          && isset( $GLOBALS['_wp_tests_wp_version'] )
9235      ) {
9236          $wp_version = $GLOBALS['_wp_tests_wp_version'];
9237      } else {
9238          $wp_version = wp_get_wp_version();
9239      }
9240  
9241      // Strip off any -alpha, -RC, -beta, -src suffixes.
9242      list( $version ) = explode( '-', $wp_version );
9243  
9244      if ( is_string( $required ) ) {
9245          $trimmed = trim( $required );
9246  
9247          if ( substr_count( $trimmed, '.' ) > 1 && str_ends_with( $trimmed, '.0' ) ) {
9248              $required = substr( $trimmed, 0, -2 );
9249          }
9250      }
9251  
9252      return empty( $required ) || version_compare( $version, $required, '>=' );
9253  }
9254  
9255  /**
9256   * Checks compatibility with the current PHP version.
9257   *
9258   * @since 5.2.0
9259   *
9260   * @param string $required Minimum required PHP version.
9261   * @return bool True if required version is compatible or empty, false if not.
9262   */
9263  function is_php_version_compatible( $required ) {
9264      return empty( $required ) || version_compare( PHP_VERSION, $required, '>=' );
9265  }
9266  
9267  /**
9268   * Checks if two numbers are nearly the same.
9269   *
9270   * This is similar to using `round()` but the precision is more fine-grained.
9271   *
9272   * @since 5.3.0
9273   *
9274   * @param int|float $expected  The expected value.
9275   * @param int|float $actual    The actual number.
9276   * @param int|float $precision Optional. The allowed variation. Default 1.
9277   * @return bool Whether the numbers match within the specified precision.
9278   */
9279  function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) {
9280      return abs( (float) $expected - (float) $actual ) <= $precision;
9281  }
9282  
9283  /**
9284   * Creates and returns the markup for an admin notice.
9285   *
9286   * @since 6.4.0
9287   *
9288   * @param string $message The message.
9289   * @param array  $args {
9290   *     Optional. An array of arguments for the admin notice. Default empty array.
9291   *
9292   *     @type string   $type               Optional. The type of admin notice.
9293   *                                        For example, 'error', 'success', 'warning', 'info'.
9294   *                                        Default empty string.
9295   *     @type bool     $dismissible        Optional. Whether the admin notice is dismissible. Default false.
9296   *     @type string   $id                 Optional. The value of the admin notice's ID attribute. Default empty string.
9297   *     @type string[] $additional_classes Optional. A string array of class names. Default empty array.
9298   *     @type string[] $attributes         Optional. Additional attributes for the notice div. Default empty array.
9299   *     @type bool     $paragraph_wrap     Optional. Whether to wrap the message in paragraph tags. Default true.
9300   * }
9301   * @return string The markup for an admin notice.
9302   */
9303  function wp_get_admin_notice( $message, $args = array() ) {
9304      $defaults = array(
9305          'type'               => '',
9306          'dismissible'        => false,
9307          'id'                 => '',
9308          'additional_classes' => array(),
9309          'attributes'         => array(),
9310          'paragraph_wrap'     => true,
9311      );
9312  
9313      $args = wp_parse_args( $args, $defaults );
9314  
9315      /**
9316       * Filters the arguments for an admin notice.
9317       *
9318       * @since 6.4.0
9319       *
9320       * @param array  $args    The arguments for the admin notice.
9321       * @param string $message The message for the admin notice.
9322       */
9323      $args = apply_filters( 'wp_admin_notice_args', $args, $message );
9324  
9325      $wrap_with_p  = false !== $args['paragraph_wrap'];
9326      $wrap_opener  = $wrap_with_p ? '<p>' : '';
9327      $wrap_closer  = $wrap_with_p ? '</p>' : '';
9328      $html_builder = new WP_HTML_Tag_Processor( "<div class=\"notice\">{$wrap_opener}" );
9329      $html_builder->next_token();
9330  
9331      if ( is_string( $args['id'] ) ) {
9332          $trimmed_id = trim( $args['id'] );
9333  
9334          if ( '' !== $trimmed_id ) {
9335              $html_builder->set_attribute( 'id', $trimmed_id );
9336          }
9337      }
9338  
9339      if ( is_string( $args['type'] ) ) {
9340          $type = trim( $args['type'] );
9341  
9342          if ( strlen( $type ) !== strcspn( $type, " \f\t\r\n" ) ) {
9343              _doing_it_wrong(
9344                  __FUNCTION__,
9345                  sprintf(
9346                      /* translators: %s: The "type" key. */
9347                      __( 'The %s key must be a string without spaces.' ),
9348                      '<code>type</code>'
9349                  ),
9350                  '6.4.0'
9351              );
9352          }
9353  
9354          if ( '' !== $type ) {
9355              $html_builder->add_class( "notice-{$type}" );
9356          }
9357      }
9358  
9359      if ( true === $args['dismissible'] ) {
9360          $html_builder->add_class( 'is-dismissible' );
9361      }
9362  
9363      if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) {
9364          foreach ( $args['additional_classes'] as $class_name ) {
9365              $html_builder->add_class( $class_name );
9366          }
9367      }
9368  
9369      if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) {
9370          foreach ( $args['attributes'] as $name => $value ) {
9371              if ( is_int( $name ) ) {
9372                  /*
9373                   * Boolean attributes may have been appended as numeric list items,
9374                   * for example, with `$args['attributes'][] = 'disabled'`. They should
9375                   * be recorded with the value serving as their name.
9376                   */
9377                  $html_builder->set_attribute( $value, true );
9378              } elseif ( true === $value ) {
9379                  $html_builder->set_attribute( $name, true );
9380              } elseif ( false !== $value ) {
9381                  $html_builder->set_attribute( $name, trim( (string) $value ) );
9382              }
9383          }
9384      }
9385  
9386      $markup  = $html_builder->get_updated_html();
9387      $markup .= $message;
9388      $markup .= "{$wrap_closer}</div>";
9389  
9390      /**
9391       * Filters the markup for an admin notice.
9392       *
9393       * @since 6.4.0
9394       *
9395       * @param string $markup  The HTML markup for the admin notice.
9396       * @param string $message The message for the admin notice.
9397       * @param array  $args    The arguments for the admin notice.
9398       */
9399      return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args );
9400  }
9401  
9402  /**
9403   * Outputs an admin notice.
9404   *
9405   * @since 6.4.0
9406   *
9407   * @param string $message The message to output.
9408   * @param array  $args {
9409   *     Optional. An array of arguments for the admin notice. Default empty array.
9410   *
9411   *     @type string   $type               Optional. The type of admin notice.
9412   *                                        For example, 'error', 'success', 'warning', 'info'.
9413   *                                        Default empty string.
9414   *     @type bool     $dismissible        Optional. Whether the admin notice is dismissible. Default false.
9415   *     @type string   $id                 Optional. The value of the admin notice's ID attribute. Default empty string.
9416   *     @type string[] $additional_classes Optional. A string array of class names. Default empty array.
9417   *     @type string[] $attributes         Optional. Additional attributes for the notice div. Default empty array.
9418   *     @type bool     $paragraph_wrap     Optional. Whether to wrap the message in paragraph tags. Default true.
9419   * }
9420   */
9421  function wp_admin_notice( $message, $args = array() ) {
9422      /**
9423       * Fires before an admin notice is output.
9424       *
9425       * @since 6.4.0
9426       *
9427       * @param string $message The message for the admin notice.
9428       * @param array  $args    The arguments for the admin notice.
9429       */
9430      do_action( 'wp_admin_notice', $message, $args );
9431  
9432      echo wp_kses_post( wp_get_admin_notice( $message, $args ) );
9433  }
9434  
9435  /**
9436   * Checks if a mime type is for a HEIC/HEIF image.
9437   *
9438   * @since 6.7.0
9439   *
9440   * @param string $mime_type The mime type to check.
9441   * @return bool Whether the mime type is for a HEIC/HEIF image.
9442   */
9443  function wp_is_heic_image_mime_type( $mime_type ) {
9444      $heic_mime_types = array(
9445          'image/heic',
9446          'image/heif',
9447          'image/heic-sequence',
9448          'image/heif-sequence',
9449      );
9450  
9451      return in_array( $mime_type, $heic_mime_types, true );
9452  }
9453  
9454  /**
9455   * Returns a cryptographically secure hash of a message using a fast generic hash function.
9456   *
9457   * Use the wp_verify_fast_hash() function to verify the hash.
9458   *
9459   * This function does not salt the value prior to being hashed, therefore input to this function must originate from
9460   * a random generator with sufficiently high entropy, preferably greater than 128 bits. This function is used internally
9461   * in WordPress to hash security keys and application passwords which are generated with high entropy.
9462   *
9463   * Important:
9464   *
9465   *  - This function must not be used for hashing user-generated passwords. Use wp_hash_password() for that.
9466   *  - This function must not be used for hashing other low-entropy input. Use wp_hash() for that.
9467   *
9468   * The BLAKE2b algorithm is used by Sodium to hash the message.
9469   *
9470   * @since 6.8.0
9471   *
9472   * @throws TypeError Thrown by Sodium if the message is not a string.
9473   *
9474   * @param string $message The message to hash.
9475   * @return string The hash of the message.
9476   */
9477  function wp_fast_hash(
9478      #[\SensitiveParameter]
9479      string $message
9480  ): string {
9481      $hashed = sodium_crypto_generichash( $message, 'wp_fast_hash_6.8+', 30 );
9482      return '$generic$' . sodium_bin2base64( $hashed, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING );
9483  }
9484  
9485  /**
9486   * Checks whether a plaintext message matches the hashed value. Used to verify values hashed via wp_fast_hash().
9487   *
9488   * The function uses Sodium to hash the message and compare it to the hashed value. If the hash is not a generic hash,
9489   * the hash is treated as a phpass portable hash in order to provide backward compatibility for passwords and security
9490   * keys which were hashed using phpass prior to WordPress 6.8.0.
9491   *
9492   * @since 6.8.0
9493   *
9494   * @throws TypeError Thrown by Sodium if the message is not a string.
9495   *
9496   * @param string $message The plaintext message.
9497   * @param string $hash    Hash of the message to check against.
9498   * @return bool Whether the message matches the hashed message.
9499   */
9500  function wp_verify_fast_hash(
9501      #[\SensitiveParameter]
9502      string $message,
9503      string $hash
9504  ): bool {
9505      if ( ! str_starts_with( $hash, '$generic$' ) ) {
9506          // Back-compat for old phpass hashes.
9507          require_once  ABSPATH . WPINC . '/class-phpass.php';
9508          return ( new PasswordHash( 8, true ) )->CheckPassword( $message, $hash );
9509      }
9510  
9511      return hash_equals( $hash, wp_fast_hash( $message ) );
9512  }
9513  
9514  /**
9515   * Sends an email to the user when a new application password is created.
9516   *
9517   * @since 7.2.0
9518   *
9519   * @param int   $user_id  The user ID.
9520   * @param array $new_item The application password details.
9521   */
9522  function wp_application_password_created_notification( $user_id, $new_item ) {
9523      $send = true;
9524  
9525      // Get current user data.
9526      $user = get_userdata( $user_id );
9527  
9528      if ( ! $user ) {
9529          return;
9530      }
9531  
9532      if ( ! is_email( $user->user_email ) ) {
9533          return;
9534      }
9535  
9536      // Validate that the application password has a name.
9537      if ( empty( $new_item['name'] ) ) {
9538          return;
9539      }
9540  
9541      /**
9542       * Filters whether to send the application password created notification email.
9543       *
9544       * @since 7.2.0
9545       *
9546       * @param bool    $send  Whether to send the email notification.
9547       * @param WP_User $user  The user object.
9548       * @param array   $new_item The application password details.
9549       */
9550      $send = apply_filters( 'wp_send_application_password_created_email', $send, $user, $new_item );
9551  
9552      if ( ! $send ) {
9553          return;
9554      }
9555  
9556      /* translators: Do not translate USERNAME, APPLICATION_PASSWORD_NAME, SITENAME, SITEURL, EMAIL: those are placeholders. */
9557      $application_password_create_text = __(
9558          'Hi ###USERNAME###,
9559  
9560  A new application password was added to your account on ###SITENAME###. This password allows access to your account via the REST API.
9561  
9562  If you did not expect this, please contact the Site Administrator at
9563  ###ADMIN_EMAIL###
9564  
9565  Application password name: ###APPLICATION_PASSWORD_NAME###
9566  Site: ###SITEURL###
9567  
9568  You can manage your application passwords in your account settings.
9569  
9570  This email has been sent to ###EMAIL###
9571  
9572  Regards,
9573  All at ###SITENAME###
9574  ###SITEURL###'
9575      );
9576  
9577      $email = array(
9578          'to'      => $user->user_email,
9579          /* translators: Application password creation email subject. %s: Site title. */
9580          'subject' => __( '[%s] Application Password Created' ),
9581          'message' => $application_password_create_text,
9582          'headers' => '',
9583      );
9584  
9585      // Get site name.
9586      $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
9587  
9588      /**
9589       * Filters the contents of the email notification sent to a user when a new application password is created.
9590       *
9591       * @since 7.2.0
9592       *
9593       * @param array   $email {
9594       *     Used to build wp_mail().
9595       *
9596       *     @type string $to      The email address of the intended recipient.
9597       *     @type string $subject The subject of the email.
9598       *     @type string $message The content of the email.
9599       *         The following strings have a special meaning and will get replaced dynamically:
9600       *          - `###USERNAME###`                  The user's display name.
9601       *          - `###APPLICATION_PASSWORD_NAME###` The name of the application password.
9602       *          - `###EMAIL###`                     The user's email address.
9603       *          - `###SITENAME###`                  The name of the site.
9604       *          - `###SITEURL###`                   The URL to the site.
9605       *     @type string $headers Headers.
9606       * }
9607       * @param WP_User $user     The user object.
9608       * @param array   $new_item The application password details.
9609       */
9610      $email = apply_filters( 'wp_application_password_created_email', $email, $user, $new_item );
9611  
9612      $email['message'] = str_replace( '###USERNAME###', $user->display_name, $email['message'] );
9613      $email['message'] = str_replace( '###APPLICATION_PASSWORD_NAME###', $new_item['name'], $email['message'] );
9614      $email['message'] = str_replace( '###EMAIL###', $user->user_email, $email['message'] );
9615      $email['message'] = str_replace( '###SITENAME###', $site_name, $email['message'] );
9616      $email['message'] = str_replace( '###SITEURL###', home_url(), $email['message'] );
9617  
9618      wp_mail(
9619          $email['to'],
9620          sprintf(
9621              $email['subject'],
9622              $site_name
9623          ),
9624          $email['message'],
9625          $email['headers']
9626      );
9627  }


Generated : Tue Oct 6 08:20:33 2026 Cross-referenced by PHPXref