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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref