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