[ Index ]

PHP Cross Reference of WordPress

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


Generated: Tue Mar 19 01:00:02 2024 Cross-referenced by PHPXref 0.7.1