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