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