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