[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> functions.php (source)

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


Generated : Thu Apr 24 08:20:01 2025 Cross-referenced by PHPXref