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