[ 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.