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