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


Generated : Sun Dec 22 08:20:01 2024 Cross-referenced by PHPXref