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