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


Generated : Sat Jul 18 08:20:16 2026 Cross-referenced by PHPXref