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


Generated : Mon Mar 31 08:20:01 2025 Cross-referenced by PHPXref