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