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


Generated : Fri Jul 25 08:20:01 2025 Cross-referenced by PHPXref