[ 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.