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