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


Generated : Sun Jun 28 08:20:12 2026 Cross-referenced by PHPXref