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