[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Thu Oct 2 08:20:03 2025 Cross-referenced by PHPXref