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


Generated : Mon Oct 26 08:20:34 2026 Cross-referenced by PHPXref