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