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


Generated : Wed Sep 16 08:20:31 2026 Cross-referenced by PHPXref