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