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