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


Generated : Fri Aug 7 08:20:20 2026 Cross-referenced by PHPXref