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


Generated : Thu Sep 4 08:20:02 2025 Cross-referenced by PHPXref