diff -r 3d4e9c994f10 -r a86126ab1dd4 wp/wp-includes/functions.php --- a/wp/wp-includes/functions.php Tue Oct 22 16:11:46 2019 +0200 +++ b/wp/wp-includes/functions.php Tue Dec 15 13:49:49 2020 +0100 @@ -5,51 +5,57 @@ * @package WordPress */ -require( ABSPATH . WPINC . '/option.php' ); - -/** - * Convert given date string into a different format. - * - * $format should be either a PHP date format string, e.g. 'U' for a Unix - * timestamp, or 'G' for a Unix timestamp assuming that $date is GMT. - * - * If $translate is true then the given date and format string will - * be passed to date_i18n() for translation. +require ABSPATH . WPINC . '/option.php'; + +/** + * Convert given MySQL date string into a different format. + * + * `$format` should be a PHP date format string. + * 'U' and 'G' formats will return a sum of timestamp with timezone offset. + * `$date` is expected to be local time in MySQL format (`Y-m-d H:i:s`). + * + * Historically UTC time could be passed to the function to produce Unix timestamp. + * + * If `$translate` is true then the given date and format string will + * be passed to `wp_date()` for translation. * * @since 0.71 * * @param string $format Format of the date to return. * @param string $date Date string to convert. * @param bool $translate Whether the return date should be translated. Default true. - * @return string|int|bool Formatted date string or Unix timestamp. False if $date is empty. + * @return string|int|false Formatted date string or sum of Unix timestamp and timezone offset. + * False on failure. */ function mysql2date( $format, $date, $translate = true ) { if ( empty( $date ) ) { return false; } - if ( 'G' == $format ) { - return strtotime( $date . ' +0000' ); - } - - $i = strtotime( $date ); - - if ( 'U' == $format ) { - return $i; + $datetime = date_create( $date, wp_timezone() ); + + if ( false === $datetime ) { + return false; + } + + // Returns a sum of timestamp with timezone offset. Ideally should never be used. + if ( 'G' === $format || 'U' === $format ) { + return $datetime->getTimestamp() + $datetime->getOffset(); } if ( $translate ) { - return date_i18n( $format, $i ); - } else { - return date( $format, $i ); - } -} - -/** - * Retrieve the current time based on specified type. + return wp_date( $format, $datetime->getTimestamp() ); + } + + return $datetime->format( $format ); +} + +/** + * Retrieves the current time based on specified type. * * The 'mysql' type will return the time in the format for MySQL DATETIME field. - * The 'timestamp' type will return the current timestamp. + * The 'timestamp' type will return the current timestamp or a sum of timestamp + * and timezone offset, depending on `$gmt`. * Other strings will be interpreted as PHP date formats (e.g. 'Y-m-d'). * * If $gmt is set to either '1' or 'true', then both types will use GMT time. @@ -57,141 +63,245 @@ * * @since 1.0.0 * - * @param string $type Type of time to retrieve. Accepts 'mysql', 'timestamp', or PHP date - * format string (e.g. 'Y-m-d'). + * @param string $type Type of time to retrieve. Accepts 'mysql', 'timestamp', + * or PHP date format string (e.g. 'Y-m-d'). * @param int|bool $gmt Optional. Whether to use GMT timezone. Default false. * @return int|string Integer if $type is 'timestamp', string otherwise. */ function current_time( $type, $gmt = 0 ) { - switch ( $type ) { - case 'mysql': - return ( $gmt ) ? gmdate( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s', ( time() + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ); - case 'timestamp': - return ( $gmt ) ? time() : time() + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); - default: - return ( $gmt ) ? gmdate( $type ) : gmdate( $type, time() + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ); - } -} - -/** - * Retrieve the date in localized format, based on a sum of Unix timestamp and + // Don't use non-GMT timestamp, unless you know the difference and really need to. + if ( 'timestamp' === $type || 'U' === $type ) { + return $gmt ? time() : time() + (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); + } + + if ( 'mysql' === $type ) { + $type = 'Y-m-d H:i:s'; + } + + $timezone = $gmt ? new DateTimeZone( 'UTC' ) : wp_timezone(); + $datetime = new DateTime( 'now', $timezone ); + + return $datetime->format( $type ); +} + +/** + * Retrieves the current time as an object with the timezone from settings. + * + * @since 5.3.0 + * + * @return DateTimeImmutable Date and time object. + */ +function current_datetime() { + return new DateTimeImmutable( 'now', wp_timezone() ); +} + +/** + * Retrieves the timezone from site settings as a string. + * + * Uses the `timezone_string` option to get a proper timezone if available, + * otherwise falls back to an offset. + * + * @since 5.3.0 + * + * @return string PHP timezone string or a ±HH:MM offset. + */ +function wp_timezone_string() { + $timezone_string = get_option( 'timezone_string' ); + + if ( $timezone_string ) { + return $timezone_string; + } + + $offset = (float) get_option( 'gmt_offset' ); + $hours = (int) $offset; + $minutes = ( $offset - $hours ); + + $sign = ( $offset < 0 ) ? '-' : '+'; + $abs_hour = abs( $hours ); + $abs_mins = abs( $minutes * 60 ); + $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); + + return $tz_offset; +} + +/** + * Retrieves the timezone from site settings as a `DateTimeZone` object. + * + * Timezone can be based on a PHP timezone string or a ±HH:MM offset. + * + * @since 5.3.0 + * + * @return DateTimeZone Timezone object. + */ +function wp_timezone() { + return new DateTimeZone( wp_timezone_string() ); +} + +/** + * Retrieves the date in localized format, based on a sum of Unix timestamp and * timezone offset in seconds. * * If the locale specifies the locale month and weekday, then the locale will * take over the format for the date. If it isn't, then the date format string * will be used instead. * + * Note that due to the way WP typically generates a sum of timestamp and offset + * with `strtotime()`, it implies offset added at a _current_ time, not at the time + * the timestamp represents. Storing such timestamps or calculating them differently + * will lead to invalid output. + * * @since 0.71 - * - * @global WP_Locale $wp_locale - * - * @param string $dateformatstring Format to display the date. - * @param int|bool $timestamp_with_offset Optional. A sum of Unix timestamp and timezone offset in seconds. - * Default false. - * @param bool $gmt Optional. Whether to use GMT timezone. Only applies if timestamp is - * not provided. Default false. - * + * @since 5.3.0 Converted into a wrapper for wp_date(). + * + * @global WP_Locale $wp_locale WordPress date and time locale object. + * + * @param string $format Format to display the date. + * @param int|bool $timestamp_with_offset Optional. A sum of Unix timestamp and timezone offset + * in seconds. Default false. + * @param bool $gmt Optional. Whether to use GMT timezone. Only applies + * if timestamp is not provided. Default false. * @return string The date, translated if locale specifies it. */ -function date_i18n( $dateformatstring, $timestamp_with_offset = false, $gmt = false ) { - global $wp_locale; - $i = $timestamp_with_offset; - - if ( false === $i ) { - $i = current_time( 'timestamp', $gmt ); +function date_i18n( $format, $timestamp_with_offset = false, $gmt = false ) { + $timestamp = $timestamp_with_offset; + + // If timestamp is omitted it should be current time (summed with offset, unless `$gmt` is true). + if ( ! is_numeric( $timestamp ) ) { + $timestamp = current_time( 'timestamp', $gmt ); } /* - * Store original value for language with untypical grammars. - * See https://core.trac.wordpress.org/ticket/9396 + * This is a legacy implementation quirk that the returned timestamp is also with offset. + * Ideally this function should never be used to produce a timestamp. */ - $req_format = $dateformatstring; - - $dateformatstring = preg_replace( '/(?month ) ) && ( ! empty( $wp_locale->weekday ) ) ) { - $datemonth = $wp_locale->get_month( date( 'm', $i ) ); - $datemonth_abbrev = $wp_locale->get_month_abbrev( $datemonth ); - $dateweekday = $wp_locale->get_weekday( date( 'w', $i ) ); - $dateweekday_abbrev = $wp_locale->get_weekday_abbrev( $dateweekday ); - $datemeridiem = $wp_locale->get_meridiem( date( 'a', $i ) ); - $datemeridiem_capital = $wp_locale->get_meridiem( date( 'A', $i ) ); - $dateformatstring = ' ' . $dateformatstring; - $dateformatstring = preg_replace( '/([^\\\])D/', "\\1" . backslashit( $dateweekday_abbrev ), $dateformatstring ); - $dateformatstring = preg_replace( '/([^\\\])F/', "\\1" . backslashit( $datemonth ), $dateformatstring ); - $dateformatstring = preg_replace( '/([^\\\])l/', "\\1" . backslashit( $dateweekday ), $dateformatstring ); - $dateformatstring = preg_replace( '/([^\\\])M/', "\\1" . backslashit( $datemonth_abbrev ), $dateformatstring ); - $dateformatstring = preg_replace( '/([^\\\])a/', "\\1" . backslashit( $datemeridiem ), $dateformatstring ); - $dateformatstring = preg_replace( '/([^\\\])A/', "\\1" . backslashit( $datemeridiem_capital ), $dateformatstring ); - - $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) - 1 ); - } - $timezone_formats = array( 'P', 'I', 'O', 'T', 'Z', 'e' ); - $timezone_formats_re = implode( '|', $timezone_formats ); - if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) { - $timezone_string = get_option( 'timezone_string' ); - if ( false === $timestamp_with_offset && $gmt ) { - $timezone_string = 'UTC'; - } - if ( $timezone_string ) { - $timezone_object = timezone_open( $timezone_string ); - $date_object = date_create( null, $timezone_object ); - foreach ( $timezone_formats as $timezone_format ) { - if ( false !== strpos( $dateformatstring, $timezone_format ) ) { - $formatted = date_format( $date_object, $timezone_format ); - $dateformatstring = ' ' . $dateformatstring; - $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring ); - $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) - 1 ); - } - } - } else { - $offset = get_option( 'gmt_offset' ); - foreach ( $timezone_formats as $timezone_format ) { - if ( 'I' === $timezone_format ) { - continue; - } - - if ( false !== strpos( $dateformatstring, $timezone_format ) ) { - if ( 'Z' === $timezone_format ) { - $formatted = (string) ( $offset * HOUR_IN_SECONDS ); - } else { - $prefix = ''; - $hours = (int) $offset; - $separator = ''; - $minutes = abs( ( $offset - $hours ) * 60 ); - - if ( 'T' === $timezone_format ) { - $prefix = 'GMT'; - } elseif ( 'e' === $timezone_format || 'P' === $timezone_format ) { - $separator = ':'; - } - - $formatted = sprintf( '%s%+03d%s%02d', $prefix, $hours, $separator, $minutes ); - } - - $dateformatstring = ' ' . $dateformatstring; - $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring ); - $dateformatstring = substr( $dateformatstring, 1 ); - } - } - } - } - $j = @date( $dateformatstring, $i ); + if ( 'U' === $format ) { + $date = $timestamp; + } elseif ( $gmt && false === $timestamp_with_offset ) { // Current time in UTC. + $date = wp_date( $format, null, new DateTimeZone( 'UTC' ) ); + } elseif ( false === $timestamp_with_offset ) { // Current time in site's timezone. + $date = wp_date( $format ); + } else { + /* + * Timestamp with offset is typically produced by a UTC `strtotime()` call on an input without timezone. + * This is the best attempt to reverse that operation into a local time to use. + */ + $local_time = gmdate( 'Y-m-d H:i:s', $timestamp ); + $timezone = wp_timezone(); + $datetime = date_create( $local_time, $timezone ); + $date = wp_date( $format, $datetime->getTimestamp(), $timezone ); + } /** * Filters the date formatted based on the locale. * * @since 2.8.0 * - * @param string $j Formatted date string. - * @param string $req_format Format to display the date. - * @param int $i A sum of Unix timestamp and timezone offset in seconds. - * @param bool $gmt Whether to use GMT timezone. Only applies if timestamp was - * not provided. Default false. + * @param string $date Formatted date string. + * @param string $format Format to display the date. + * @param int $timestamp A sum of Unix timestamp and timezone offset in seconds. + * Might be without offset if input omitted timestamp but requested GMT. + * @param bool $gmt Whether to use GMT timezone. Only applies if timestamp was not provided. + * Default false. */ - $j = apply_filters( 'date_i18n', $j, $req_format, $i, $gmt ); - return $j; + $date = apply_filters( 'date_i18n', $date, $format, $timestamp, $gmt ); + + return $date; +} + +/** + * Retrieves the date, in localized format. + * + * This is a newer function, intended to replace `date_i18n()` without legacy quirks in it. + * + * Note that, unlike `date_i18n()`, this function accepts a true Unix timestamp, not summed + * with timezone offset. + * + * @since 5.3.0 + * + * @param string $format PHP date format. + * @param int $timestamp Optional. Unix timestamp. Defaults to current time. + * @param DateTimeZone $timezone Optional. Timezone to output result in. Defaults to timezone + * from site settings. + * @return string|false The date, translated if locale specifies it. False on invalid timestamp input. + */ +function wp_date( $format, $timestamp = null, $timezone = null ) { + global $wp_locale; + + if ( null === $timestamp ) { + $timestamp = time(); + } elseif ( ! is_numeric( $timestamp ) ) { + return false; + } + + if ( ! $timezone ) { + $timezone = wp_timezone(); + } + + $datetime = date_create( '@' . $timestamp ); + $datetime->setTimezone( $timezone ); + + if ( empty( $wp_locale->month ) || empty( $wp_locale->weekday ) ) { + $date = $datetime->format( $format ); + } else { + // We need to unpack shorthand `r` format because it has parts that might be localized. + $format = preg_replace( '/(?get_month( $datetime->format( 'm' ) ); + $weekday = $wp_locale->get_weekday( $datetime->format( 'w' ) ); + + for ( $i = 0; $i < $format_length; $i ++ ) { + switch ( $format[ $i ] ) { + case 'D': + $new_format .= addcslashes( $wp_locale->get_weekday_abbrev( $weekday ), '\\A..Za..z' ); + break; + case 'F': + $new_format .= addcslashes( $month, '\\A..Za..z' ); + break; + case 'l': + $new_format .= addcslashes( $weekday, '\\A..Za..z' ); + break; + case 'M': + $new_format .= addcslashes( $wp_locale->get_month_abbrev( $month ), '\\A..Za..z' ); + break; + case 'a': + $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'a' ) ), '\\A..Za..z' ); + break; + case 'A': + $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'A' ) ), '\\A..Za..z' ); + break; + case '\\': + $new_format .= $format[ $i ]; + + // If character follows a slash, we add it without translating. + if ( $i < $format_length ) { + $new_format .= $format[ ++$i ]; + } + break; + default: + $new_format .= $format[ $i ]; + break; + } + } + + $date = $datetime->format( $new_format ); + $date = wp_maybe_decline_date( $date, $format ); + } + + /** + * Filters the date formatted based on the locale. + * + * @since 5.3.0 + * + * @param string $date Formatted date string. + * @param string $format Format to display the date. + * @param int $timestamp Unix timestamp. + * @param DateTimeZone $timezone Timezone. + */ + $date = apply_filters( 'wp_date', $date, $format, $timestamp, $timezone ); + + return $date; } /** @@ -201,42 +311,79 @@ * formats (like 'j F Y'), the month name will be replaced with a correct form. * * @since 4.4.0 - * - * @global WP_Locale $wp_locale - * - * @param string $date Formatted date string. + * @since 5.4.0 The `$format` parameter was added. + * + * @global WP_Locale $wp_locale WordPress date and time locale object. + * + * @param string $date Formatted date string. + * @param string $format Optional. Date format to check. Default empty string. * @return string The date, declined if locale specifies it. */ -function wp_maybe_decline_date( $date ) { +function wp_maybe_decline_date( $date, $format = '' ) { global $wp_locale; - // i18n functions are not available in SHORTINIT mode + // i18n functions are not available in SHORTINIT mode. if ( ! function_exists( '_x' ) ) { return $date; } - /* translators: If months in your language require a genitive case, + /* + * translators: If months in your language require a genitive case, * translate this to 'on'. Do not translate into your own language. */ if ( 'on' === _x( 'off', 'decline months names: on or off' ) ) { - // Match a format like 'j F Y' or 'j. F' - if ( @preg_match( '#^\d{1,2}\.? [^\d ]+#u', $date ) ) { - $months = $wp_locale->month; - $months_genitive = $wp_locale->month_genitive; - + + $months = $wp_locale->month; + $months_genitive = $wp_locale->month_genitive; + + /* + * Match a format like 'j F Y' or 'j. F' (day of the month, followed by month name) + * and decline the month. + */ + if ( $format ) { + $decline = preg_match( '#[dj]\.? F#', $format ); + } else { + // If the format is not passed, try to guess it from the date string. + $decline = preg_match( '#\b\d{1,2}\.? [^\d ]+\b#u', $date ); + } + + if ( $decline ) { foreach ( $months as $key => $month ) { - $months[ $key ] = '# ' . $month . '( |$)#u'; + $months[ $key ] = '# ' . preg_quote( $month, '#' ) . '\b#u'; } foreach ( $months_genitive as $key => $month ) { - $months_genitive[ $key ] = ' ' . $month . '$1'; + $months_genitive[ $key ] = ' ' . $month; } $date = preg_replace( $months, $months_genitive, $date ); } - } - - // Used for locale-specific rules + + /* + * Match a format like 'F jS' or 'F j' (month name, followed by day with an optional ordinal suffix) + * and change it to declined 'j F'. + */ + if ( $format ) { + $decline = preg_match( '#F [dj]#', $format ); + } else { + // If the format is not passed, try to guess it from the date string. + $decline = preg_match( '#\b[^\d ]+ \d{1,2}(st|nd|rd|th)?\b#u', trim( $date ) ); + } + + if ( $decline ) { + foreach ( $months as $key => $month ) { + $months[ $key ] = '#\b' . preg_quote( $month, '#' ) . ' (\d{1,2})(st|nd|rd|th)?([-–]\d{1,2})?(st|nd|rd|th)?\b#u'; + } + + foreach ( $months_genitive as $key => $month ) { + $months_genitive[ $key ] = '$1$3 ' . $month; + } + + $date = preg_replace( $months, $months_genitive, $date ); + } + } + + // Used for locale-specific rules. $locale = get_locale(); if ( 'ca' === $locale ) { @@ -252,7 +399,7 @@ * * @since 2.3.0 * - * @global WP_Locale $wp_locale + * @global WP_Locale $wp_locale WordPress date and time locale object. * * @param float $number The number to convert based on locale. * @param int $decimals Optional. Precision of the number of decimal places. Default 0. @@ -298,19 +445,25 @@ * * @param int|string $bytes Number of bytes. Note max integer size for integers. * @param int $decimals Optional. Precision of number of decimal places. Default 0. - * @return string|false False on failure. Number string on success. + * @return string|false Number string on success, false on failure. */ function size_format( $bytes, $decimals = 0 ) { $quant = array( - 'TB' => TB_IN_BYTES, - 'GB' => GB_IN_BYTES, - 'MB' => MB_IN_BYTES, - 'KB' => KB_IN_BYTES, - 'B' => 1, + /* translators: Unit symbol for terabyte. */ + _x( 'TB', 'unit symbol' ) => TB_IN_BYTES, + /* translators: Unit symbol for gigabyte. */ + _x( 'GB', 'unit symbol' ) => GB_IN_BYTES, + /* translators: Unit symbol for megabyte. */ + _x( 'MB', 'unit symbol' ) => MB_IN_BYTES, + /* translators: Unit symbol for kilobyte. */ + _x( 'KB', 'unit symbol' ) => KB_IN_BYTES, + /* translators: Unit symbol for byte. */ + _x( 'B', 'unit symbol' ) => 1, ); if ( 0 === $bytes ) { - return number_format_i18n( 0, $decimals ) . ' B'; + /* translators: Unit symbol for byte. */ + return number_format_i18n( 0, $decimals ) . ' ' . _x( 'B', 'unit symbol' ); } foreach ( $quant as $unit => $mag ) { @@ -373,19 +526,19 @@ // Add the hour part to the string. if ( is_numeric( $hour ) ) { - /* translators: Time duration in hour or hours. */ + /* translators: %s: Time duration in hour or hours. */ $human_readable_duration[] = sprintf( _n( '%s hour', '%s hours', $hour ), (int) $hour ); } // Add the minute part to the string. if ( is_numeric( $minute ) ) { - /* translators: Time duration in minute or minutes. */ + /* translators: %s: Time duration in minute or minutes. */ $human_readable_duration[] = sprintf( _n( '%s minute', '%s minutes', $minute ), (int) $minute ); } // Add the second part to the string. if ( is_numeric( $second ) ) { - /* translators: Time duration in second or seconds. */ + /* translators: %s: Time duration in second or seconds. */ $human_readable_duration[] = sprintf( _n( '%s second', '%s seconds', $second ), (int) $second ); } @@ -415,7 +568,7 @@ $day = mktime( 0, 0, 0, $md, $mm, $my ); // The day of the week from the timestamp. - $weekday = date( 'w', $day ); + $weekday = gmdate( 'w', $day ); if ( ! is_numeric( $start_of_week ) ) { $start_of_week = get_option( 'start_of_week' ); @@ -434,18 +587,44 @@ } /** - * Unserialize value only if it was serialized. + * Serialize data, if needed. + * + * @since 2.0.5 + * + * @param string|array|object $data Data that might be serialized. + * @return mixed A scalar data. + */ +function maybe_serialize( $data ) { + if ( is_array( $data ) || is_object( $data ) ) { + return serialize( $data ); + } + + /* + * Double serialization is required for backward compatibility. + * See https://core.trac.wordpress.org/ticket/12930 + * Also the world will end. See WP 3.6.1. + */ + if ( is_serialized( $data, false ) ) { + return serialize( $data ); + } + + return $data; +} + +/** + * Unserialize data only if it was serialized. * * @since 2.0.0 * - * @param string $original Maybe unserialized original, if is needed. + * @param string $data Data that might be unserialized. * @return mixed Unserialized data can be any type. */ -function maybe_unserialize( $original ) { - if ( is_serialized( $original ) ) { // don't attempt to unserialize data that wasn't serialized going in - return @unserialize( $original ); - } - return $original; +function maybe_unserialize( $data ) { + if ( is_serialized( $data ) ) { // Don't attempt to unserialize data that wasn't serialized going in. + return @unserialize( trim( $data ) ); + } + + return $data; } /** @@ -461,12 +640,12 @@ * @return bool False if not serialized and true if it was. */ function is_serialized( $data, $strict = true ) { - // if it isn't a string, it isn't serialized. + // If it isn't a string, it isn't serialized. if ( ! is_string( $data ) ) { return false; } $data = trim( $data ); - if ( 'N;' == $data ) { + if ( 'N;' === $data ) { return true; } if ( strlen( $data ) < 4 ) { @@ -505,7 +684,7 @@ } elseif ( false === strpos( $data, '"' ) ) { return false; } - // or else fall through + // Or else fall through. case 'a': case 'O': return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data ); @@ -513,7 +692,7 @@ case 'i': case 'd': $end = $strict ? '$' : ''; - return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data ); + return (bool) preg_match( "/^{$token}:[0-9.E+-]+;$end/", $data ); } return false; } @@ -538,7 +717,7 @@ return false; } elseif ( ';' !== substr( $data, -1 ) ) { return false; - } elseif ( $data[0] !== 's' ) { + } elseif ( 's' !== $data[0] ) { return false; } elseif ( '"' !== substr( $data, -2, 1 ) ) { return false; @@ -548,29 +727,6 @@ } /** - * Serialize data, if needed. - * - * @since 2.0.5 - * - * @param string|array|object $data Data that might be serialized. - * @return mixed A scalar data - */ -function maybe_serialize( $data ) { - if ( is_array( $data ) || is_object( $data ) ) { - return serialize( $data ); - } - - // Double serialization is required for backward compatibility. - // See https://core.trac.wordpress.org/ticket/12930 - // Also the world will end. See WP 3.6.1. - if ( is_serialized( $data, false ) ) { - return serialize( $data ); - } - - return $data; -} - -/** * Retrieve post title from XMLRPC XML. * * If the title element is not part of the XML, then the default post title from @@ -639,7 +795,7 @@ * @since 3.7.0 * * @param string $content Content to extract URLs from. - * @return array URLs found in passed string. + * @return string[] Array of URLs found in passed string. */ function wp_extract_urls( $content ) { preg_match_all( @@ -672,27 +828,40 @@ * pingbacks and trackbacks. * * @since 1.5.0 + * @since 5.3.0 The `$content` parameter was made optional, and the `$post` parameter was + * updated to accept a post ID or a WP_Post object. * * @global wpdb $wpdb WordPress database abstraction object. * - * @param string $content Post Content. - * @param int $post_ID Post ID. - */ -function do_enclose( $content, $post_ID ) { + * @param string $content Post content. If `null`, the `post_content` field from `$post` is used. + * @param int|WP_Post $post Post ID or post object. + * @return null|bool Returns false if post is not found. + */ +function do_enclose( $content = null, $post ) { global $wpdb; - //TODO: Tidy this ghetto code up and make the debug code optional - include_once( ABSPATH . WPINC . '/class-IXR.php' ); + // @todo Tidy this code and make the debug code optional. + include_once ABSPATH . WPINC . '/class-IXR.php'; + + $post = get_post( $post ); + if ( ! $post ) { + return false; + } + + if ( null === $content ) { + $content = $post->post_content; + } $post_links = array(); - $pung = get_enclosed( $post_ID ); + $pung = get_enclosed( $post->ID ); $post_links_temp = wp_extract_urls( $content ); foreach ( $pung as $link_test ) { - if ( ! in_array( $link_test, $post_links_temp ) ) { // link no longer in post - $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 ) . '%' ) ); + // Link is no longer in post. + if ( ! in_array( $link_test, $post_links_temp, true ) ) { + $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 ) . '%' ) ); foreach ( $mids as $mid ) { delete_metadata_by_mid( 'post', $mid ); } @@ -700,14 +869,15 @@ } foreach ( (array) $post_links_temp as $link_test ) { - if ( ! in_array( $link_test, $pung ) ) { // If we haven't pung it already - $test = @parse_url( $link_test ); + // If we haven't pung it already. + if ( ! in_array( $link_test, $pung, true ) ) { + $test = parse_url( $link_test ); if ( false === $test ) { continue; } if ( isset( $test['query'] ) ) { $post_links[] = $link_test; - } elseif ( isset( $test['path'] ) && ( $test['path'] != '/' ) && ( $test['path'] != '' ) ) { + } elseif ( isset( $test['path'] ) && ( '/' !== $test['path'] ) && ( '' !== $test['path'] ) ) { $post_links[] = $link_test; } } @@ -721,23 +891,23 @@ * * @since 4.4.0 * - * @param array $post_links An array of enclosure links. - * @param int $post_ID Post ID. + * @param string[] $post_links An array of enclosure links. + * @param int $post_ID Post ID. */ - $post_links = apply_filters( 'enclosure_links', $post_links, $post_ID ); + $post_links = apply_filters( 'enclosure_links', $post_links, $post->ID ); foreach ( (array) $post_links as $url ) { - 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 ) . '%' ) ) ) { - - if ( $headers = wp_get_http_headers( $url ) ) { + 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 ) . '%' ) ) ) { + + $headers = wp_get_http_headers( $url ); + if ( $headers ) { $len = isset( $headers['content-length'] ) ? (int) $headers['content-length'] : 0; $type = isset( $headers['content-type'] ) ? $headers['content-type'] : ''; $allowed_types = array( 'video', 'audio' ); - // Check to see if we can figure out the mime type from - // the extension - $url_parts = @parse_url( $url ); - if ( false !== $url_parts ) { + // Check to see if we can figure out the mime type from the extension. + $url_parts = parse_url( $url ); + if ( false !== $url_parts && ! empty( $url_parts['path'] ) ) { $extension = pathinfo( $url_parts['path'], PATHINFO_EXTENSION ); if ( ! empty( $extension ) ) { foreach ( wp_get_mime_types() as $exts => $mime ) { @@ -749,8 +919,8 @@ } } - if ( in_array( substr( $type, 0, strpos( $type, '/' ) ), $allowed_types ) ) { - add_post_meta( $post_ID, 'enclosure', "$url\n$len\n$mime\n" ); + if ( in_array( substr( $type, 0, strpos( $type, '/' ) ), $allowed_types, true ) ) { + add_post_meta( $post->ID, 'enclosure', "$url\n$len\n$mime\n" ); } } } @@ -797,7 +967,8 @@ */ function is_new_day() { global $currentday, $previousday; - if ( $currentday != $previousday ) { + + if ( $currentday !== $previousday ) { return 1; } else { return 0; @@ -813,7 +984,7 @@ * @since 2.3.0 * * @see _http_build_query() Used to build the query - * @link https://secure.php.net/manual/en/function.http-build-query.php for more on what + * @link https://www.php.net/manual/en/function.http-build-query.php for more on what * http_build_query() does. * * @param array $data URL-encode key/value pairs. @@ -829,16 +1000,15 @@ * @since 3.2.0 * @access private * - * @see https://secure.php.net/manual/en/function.http-build-query.php - * - * @param array|object $data An array or object of data. Converted to array. - * @param string $prefix Optional. Numeric index. If set, start parameter numbering with it. - * Default null. - * @param string $sep Optional. Argument separator; defaults to 'arg_separator.output'. - * Default null. - * @param string $key Optional. Used to prefix key name. Default empty. - * @param bool $urlencode Optional. Whether to use urlencode() in the result. Default true. - * + * @see https://www.php.net/manual/en/function.http-build-query.php + * + * @param array|object $data An array or object of data. Converted to array. + * @param string $prefix Optional. Numeric index. If set, start parameter numbering with it. + * Default null. + * @param string $sep Optional. Argument separator; defaults to 'arg_separator.output'. + * Default null. + * @param string $key Optional. Used to prefix key name. Default empty. + * @param bool $urlencode Optional. Whether to use urlencode() in the result. Default true. * @return string The query string. */ function _http_build_query( $data, $prefix = null, $sep = null, $key = '', $urlencode = true ) { @@ -848,15 +1018,15 @@ if ( $urlencode ) { $k = urlencode( $k ); } - if ( is_int( $k ) && $prefix != null ) { + if ( is_int( $k ) && null != $prefix ) { $k = $prefix . $k; } if ( ! empty( $key ) ) { $k = $key . '%5B' . $k . '%5D'; } - if ( $v === null ) { + if ( null === $v ) { continue; - } elseif ( $v === false ) { + } elseif ( false === $v ) { $v = '0'; } @@ -905,14 +1075,15 @@ * (XSS) attacks. * * @since 1.5.0 + * @since 5.3.0 Formalized the existing and already documented parameters + * by adding `...$args` to the function signature. * * @param string|array $key Either a query variable key, or an associative array of query variables. * @param string $value Optional. Either a query variable value, or a URL to act upon. * @param string $url Optional. A URL to act upon. * @return string New URL query string (unescaped). */ -function add_query_arg() { - $args = func_get_args(); +function add_query_arg( ...$args ) { if ( is_array( $args[0] ) ) { if ( count( $args ) < 2 || false === $args[1] ) { $uri = $_SERVER['REQUEST_URI']; @@ -927,7 +1098,8 @@ } } - if ( $frag = strstr( $uri, '#' ) ) { + $frag = strstr( $uri, '#' ); + if ( $frag ) { $uri = substr( $uri, 0, -strlen( $frag ) ); } else { $frag = ''; @@ -955,7 +1127,7 @@ } wp_parse_str( $query, $qs ); - $qs = urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string + $qs = urlencode_deep( $qs ); // This re-URL-encodes things that were already in the query string. if ( is_array( $args[0] ) ) { foreach ( $args[0] as $k => $v ) { $qs[ $k ] = $v; @@ -965,7 +1137,7 @@ } foreach ( $qs as $k => $v ) { - if ( $v === false ) { + if ( false === $v ) { unset( $qs[ $k ] ); } } @@ -988,7 +1160,7 @@ * @return string New URL query string. */ function remove_query_arg( $key, $query = false ) { - if ( is_array( $key ) ) { // removing multiple keys + if ( is_array( $key ) ) { // Removing multiple keys. foreach ( $key as $k ) { $query = add_query_arg( $k, false, $query ); } @@ -1002,16 +1174,19 @@ * * @since 4.4.0 * - * @return array An array of parameters to remove from the URL. + * @return string[] An array of parameters to remove from the URL. */ function wp_removable_query_args() { $removable_query_args = array( 'activate', 'activated', + 'admin_email_remind_later', 'approved', 'deactivate', + 'delete_count', 'deleted', 'disabled', + 'doing_wp_cron', 'enabled', 'error', 'hotkeys_highlight_first', @@ -1036,7 +1211,7 @@ * * @since 4.2.0 * - * @param array $removable_query_args An array of query variables to remove from a URL. + * @param string[] $removable_query_args An array of query variables to remove from a URL. */ return apply_filters( 'removable_query_args', $removable_query_args ); } @@ -1045,6 +1220,7 @@ * Walks the array while sanitizing the contents. * * @since 0.71 + * @since 5.5.0 Non-string values are left untouched. * * @param array $array Array to walk while sanitizing contents. * @return array Sanitized $array. @@ -1053,10 +1229,13 @@ foreach ( (array) $array as $k => $v ) { if ( is_array( $v ) ) { $array[ $k ] = add_magic_quotes( $v ); - } else { + } elseif ( is_string( $v ) ) { $array[ $k ] = addslashes( $v ); + } else { + continue; } } + return $array; } @@ -1068,10 +1247,10 @@ * @see wp_safe_remote_get() * * @param string $uri URI/URL of web page to retrieve. - * @return false|string HTTP content. False on failure. + * @return string|false HTTP content. False on failure. */ function wp_remote_fopen( $uri ) { - $parsed_url = @parse_url( $uri ); + $parsed_url = parse_url( $uri ); if ( ! $parsed_url || ! is_array( $parsed_url ) ) { return false; @@ -1094,14 +1273,15 @@ * * @since 2.0.0 * - * @global WP $wp_locale - * @global WP_Query $wp_query - * @global WP_Query $wp_the_query + * @global WP $wp Current WordPress environment instance. + * @global WP_Query $wp_query WordPress Query object. + * @global WP_Query $wp_the_query Copy of the WordPress Query object. * * @param string|array $query_vars Default WP_Query arguments. */ function wp( $query_vars = '' ) { global $wp, $wp_query, $wp_the_query; + $wp->main( $query_vars ); if ( ! isset( $wp_the_query ) ) { @@ -1120,7 +1300,7 @@ * @global array $wp_header_to_desc * * @param int $code HTTP status code. - * @return string Empty string if not found, or description if found. + * @return string Status description if found, an empty string otherwise. */ function get_status_header_desc( $code ) { global $wp_header_to_desc; @@ -1240,7 +1420,9 @@ $status_header = apply_filters( 'status_header', $status_header, $code, $description, $protocol ); } - @header( $status_header, true, $code ); + if ( ! headers_sent() ) { + header( $status_header, true, $code ); + } } /** @@ -1292,26 +1474,18 @@ * @see wp_get_nocache_headers() */ function nocache_headers() { + if ( headers_sent() ) { + return; + } + $headers = wp_get_nocache_headers(); unset( $headers['Last-Modified'] ); - // In PHP 5.3+, make sure we are not sending a Last-Modified header. - if ( function_exists( 'header_remove' ) ) { - @header_remove( 'Last-Modified' ); - } else { - // In PHP 5.2, send an empty Last-Modified header, but only as a - // last resort to override a header already sent. #WP23021 - foreach ( headers_list() as $header ) { - if ( 0 === stripos( $header, 'Last-Modified' ) ) { - $headers['Last-Modified'] = ''; - break; - } - } - } + header_remove( 'Last-Modified' ); foreach ( $headers as $name => $field_value ) { - @header( "{$name}: {$field_value}" ); + header( "{$name}: {$field_value}" ); } } @@ -1324,7 +1498,7 @@ $expiresOffset = 10 * DAY_IN_SECONDS; header( 'Content-Type: text/javascript; charset=' . get_bloginfo( 'charset' ) ); - header( 'Vary: Accept-Encoding' ); // Handle proxies + header( 'Vary: Accept-Encoding' ); // Handle proxies. header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expiresOffset ) . ' GMT' ); } @@ -1353,7 +1527,7 @@ * @return bool True if yes, false on anything else. */ function bool_from_yn( $yn ) { - return ( strtolower( $yn ) == 'y' ); + return ( 'y' === strtolower( $yn ) ); } /** @@ -1366,7 +1540,7 @@ * * @since 2.1.0 * - * @global WP_Query $wp_query Used to tell if the use a comment feed. + * @global WP_Query $wp_query WordPress Query object. */ function do_feed() { global $wp_query; @@ -1376,12 +1550,12 @@ // Remove the pad, if present. $feed = preg_replace( '/^_+/', '', $feed ); - if ( $feed == '' || $feed == 'feed' ) { + if ( '' === $feed || 'feed' === $feed ) { $feed = get_default_feed(); } if ( ! has_action( "do_feed_{$feed}" ) ) { - wp_die( __( 'ERROR: This is not a valid feed template.' ), '', array( 'response' => 404 ) ); + wp_die( __( 'Error: This is not a valid feed template.' ), '', array( 'response' => 404 ) ); } /** @@ -1456,12 +1630,11 @@ } /** - * Display the robots.txt file content. - * - * The echo content should be with usage of the permalinks or for creating the - * robots.txt file. + * Displays the default robots.txt file content. * * @since 2.1.0 + * @since 5.3.0 Remove the "Disallow: /" output if search engine visiblity is + * discouraged in favor of robots meta HTML tag in wp_no_robots(). */ function do_robots() { header( 'Content-Type: text/plain; charset=utf-8' ); @@ -1475,27 +1648,41 @@ $output = "User-agent: *\n"; $public = get_option( 'blog_public' ); - if ( '0' == $public ) { - $output .= "Disallow: /\n"; - } else { - $site_url = parse_url( site_url() ); - $path = ( ! empty( $site_url['path'] ) ) ? $site_url['path'] : ''; - $output .= "Disallow: $path/wp-admin/\n"; - $output .= "Allow: $path/wp-admin/admin-ajax.php\n"; - } + + $site_url = parse_url( site_url() ); + $path = ( ! empty( $site_url['path'] ) ) ? $site_url['path'] : ''; + $output .= "Disallow: $path/wp-admin/\n"; + $output .= "Allow: $path/wp-admin/admin-ajax.php\n"; /** * Filters the robots.txt output. * * @since 3.0.0 * - * @param string $output Robots.txt output. + * @param string $output The robots.txt output. * @param bool $public Whether the site is considered "public". */ echo apply_filters( 'robots_txt', $output, $public ); } /** + * Display the favicon.ico file content. + * + * @since 5.4.0 + */ +function do_favicon() { + /** + * Fires when serving the favicon.ico file. + * + * @since 5.4.0 + */ + do_action( 'do_faviconico' ); + + wp_redirect( get_site_icon_url( 32, includes_url( 'images/w-logo-blue-white-bg.png' ) ) ); + exit; +} + +/** * Determines whether WordPress is already installed. * * The cache will be checked first. If you have a cache plugin, which saves @@ -1529,7 +1716,7 @@ if ( ! wp_installing() ) { $alloptions = wp_load_alloptions(); } - // If siteurl is not set to autoload, check it specifically + // If siteurl is not set to autoload, check it specifically. if ( ! isset( $alloptions['siteurl'] ) ) { $installed = $wpdb->get_var( "SELECT option_value FROM $wpdb->options WHERE option_name = 'siteurl'" ); } else { @@ -1576,7 +1763,7 @@ // Die with a DB error. $wpdb->error = sprintf( - /* translators: %s: database repair URL */ + /* translators: %s: Database repair URL. */ __( 'One or more database tables are unavailable. The database may need to be repaired.' ), 'maint/repair.php?referrer=is_blog_installed' ); @@ -1664,6 +1851,7 @@ if ( $echo ) { echo $referer_field; } + return $referer_field; } @@ -1682,13 +1870,18 @@ * @return string Original referer field. */ function wp_original_referer_field( $echo = true, $jump_back_to = 'current' ) { - if ( ! $ref = wp_get_original_referer() ) { - $ref = 'previous' == $jump_back_to ? wp_get_referer() : wp_unslash( $_SERVER['REQUEST_URI'] ); - } + $ref = wp_get_original_referer(); + + if ( ! $ref ) { + $ref = ( 'previous' === $jump_back_to ) ? wp_get_referer() : wp_unslash( $_SERVER['REQUEST_URI'] ); + } + $orig_referer_field = ''; + if ( $echo ) { echo $orig_referer_field; } + return $orig_referer_field; } @@ -1699,7 +1892,7 @@ * * @since 2.0.4 * - * @return false|string False on failure. Referer URL on success. + * @return string|false Referer URL on success, false on failure. */ function wp_get_referer() { if ( ! function_exists( 'wp_validate_redirect' ) ) { @@ -1708,7 +1901,7 @@ $ref = wp_get_raw_referer(); - if ( $ref && $ref !== wp_unslash( $_SERVER['REQUEST_URI'] ) && $ref !== home_url() . wp_unslash( $_SERVER['REQUEST_URI'] ) ) { + if ( $ref && wp_unslash( $_SERVER['REQUEST_URI'] ) !== $ref && home_url() . wp_unslash( $_SERVER['REQUEST_URI'] ) !== $ref ) { return wp_validate_redirect( $ref, false ); } @@ -1739,12 +1932,13 @@ * * @since 2.0.4 * - * @return string|false False if no original referer or original referer if set. + * @return string|false Original referer URL on success, false on failure. */ function wp_get_original_referer() { if ( ! empty( $_REQUEST['_wp_original_http_referer'] ) && function_exists( 'wp_validate_redirect' ) ) { return wp_validate_redirect( wp_unslash( $_REQUEST['_wp_original_http_referer'] ), false ); } + return false; } @@ -1770,7 +1964,7 @@ $target = str_replace( '//', '/', $target ); // Put the wrapper back on the target. - if ( $wrapper !== null ) { + if ( null !== $wrapper ) { $target = $wrapper . '://' . $target; } @@ -1794,12 +1988,13 @@ // We need to find the permissions of the parent folder that exists and inherit that. $target_parent = dirname( $target ); - while ( '.' != $target_parent && ! is_dir( $target_parent ) && dirname( $target_parent ) !== $target_parent ) { + while ( '.' !== $target_parent && ! is_dir( $target_parent ) && dirname( $target_parent ) !== $target_parent ) { $target_parent = dirname( $target_parent ); } // Get the permission bits. - if ( $stat = @stat( $target_parent ) ) { + $stat = @stat( $target_parent ); + if ( $stat ) { $dir_perms = $stat['mode'] & 0007777; } else { $dir_perms = 0777; @@ -1811,10 +2006,10 @@ * If a umask is set that modifies $dir_perms, we'll have to re-set * the $dir_perms correctly with chmod() */ - if ( $dir_perms != ( $dir_perms & ~umask() ) ) { + if ( ( $dir_perms & ~umask() ) != $dir_perms ) { $folder_parts = explode( '/', substr( $target, strlen( $target_parent ) + 1 ) ); for ( $i = 1, $c = count( $folder_parts ); $i <= $c; $i++ ) { - @chmod( $target_parent . '/' . implode( '/', array_slice( $folder_parts, 0, $i ) ), $dir_perms ); + chmod( $target_parent . '/' . implode( '/', array_slice( $folder_parts, 0, $i ) ), $dir_perms ); } } @@ -1851,7 +2046,7 @@ return true; } - if ( strlen( $path ) == 0 || $path[0] == '.' ) { + if ( strlen( $path ) == 0 || '.' === $path[0] ) { return false; } @@ -1861,7 +2056,7 @@ } // A path starting with / or \ is absolute; anything else is relative. - return ( $path[0] == '/' || $path[0] == '\\' ); + return ( '/' === $path[0] || '\\' === $path[0] ); } /** @@ -1902,18 +2097,20 @@ */ function wp_normalize_path( $path ) { $wrapper = ''; + if ( wp_is_stream( $path ) ) { list( $wrapper, $path ) = explode( '://', $path, 2 ); - $wrapper .= '://'; - } - - // Standardise all paths to use / + + $wrapper .= '://'; + } + + // Standardise all paths to use '/'. $path = str_replace( '\\', '/', $path ); // Replace multiple slashes down to a singular, allowing for network shares having two slashes. $path = preg_replace( '|(?<=.)/+|', '/', $path ); - // Windows paths should uppercase the drive letter + // Windows paths should uppercase the drive letter. if ( ':' === substr( $path, 1, 1 ) ) { $path = ucfirst( $path ); } @@ -1933,8 +2130,6 @@ * * @since 2.5.0 * - * @staticvar string $temp - * * @return string Writable temporary directory. */ function get_temp_dir() { @@ -2005,22 +2200,27 @@ * @return bool Whether the path is writable. */ function win_is_writable( $path ) { - - if ( $path[ strlen( $path ) - 1 ] == '/' ) { // if it looks like a directory, check a random file within the directory + if ( '/' === $path[ strlen( $path ) - 1 ] ) { + // If it looks like a directory, check a random file within the directory. return win_is_writable( $path . uniqid( mt_rand() ) . '.tmp' ); - } elseif ( is_dir( $path ) ) { // If it's a directory (and not a file) check a random file within the directory + } elseif ( is_dir( $path ) ) { + // If it's a directory (and not a file), check a random file within the directory. return win_is_writable( $path . '/' . uniqid( mt_rand() ) . '.tmp' ); } - // check tmp file for read/write capabilities + + // Check tmp file for read/write capabilities. $should_delete_tmp_file = ! file_exists( $path ); - $f = @fopen( $path, 'a' ); - if ( $f === false ) { + + $f = @fopen( $path, 'a' ); + if ( false === $f ) { return false; } fclose( $f ); + if ( $should_delete_tmp_file ) { unlink( $path ); } + return true; } @@ -2042,7 +2242,7 @@ } /** - * Get an array containing the current upload directory's path and url. + * Returns an array containing the current upload directory's path and URL. * * Checks the 'upload_path' option, which should be from the web root folder, * and if it isn't empty it will be used. If it is empty, then the path will be @@ -2060,25 +2260,23 @@ * 'error' containing the error message. The error suggests that the parent * directory is not writable by the server. * - * On success, the returned array will have many indices: - * 'path' - base directory and sub directory or full path to upload directory. - * 'url' - base url and sub directory or absolute URL to upload directory. - * 'subdir' - sub directory if uploads use year/month folders option is on. - * 'basedir' - path without subdir. - * 'baseurl' - URL path without subdir. - * 'error' - false or error message. - * * @since 2.0.0 * @uses _wp_upload_dir() * - * @staticvar array $cache - * @staticvar array $tested_paths - * * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. * @param bool $create_dir Optional. Whether to check and create the uploads directory. * Default true for backward compatibility. * @param bool $refresh_cache Optional. Whether to refresh the cache. Default false. - * @return array See above for description. + * @return array { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to upload directory. + * @type string $url Base URL and subdirectory or absolute URL to upload directory. + * @type string $subdir Subdirectory if uploads use year/month folders option is on. + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } */ function wp_upload_dir( $time = null, $create_dir = true, $refresh_cache = false ) { static $cache = array(), $tested_paths = array(); @@ -2094,8 +2292,16 @@ * * @since 2.0.0 * - * @param array $uploads Array of upload directory data with keys of 'path', - * 'url', 'subdir, 'basedir', and 'error'. + * @param array $uploads { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to upload directory. + * @type string $url Base URL and subdirectory or absolute URL to upload directory. + * @type string $subdir Subdirectory if uploads use year/month folders option is on. + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } */ $uploads = apply_filters( 'upload_dir', $cache[ $key ] ); @@ -2113,7 +2319,7 @@ } $uploads['error'] = sprintf( - /* translators: %s: directory path */ + /* translators: %s: Directory path. */ __( 'Unable to create directory %s. Is its parent directory writable by the server?' ), esc_html( $error_path ) ); @@ -2139,17 +2345,18 @@ $siteurl = get_option( 'siteurl' ); $upload_path = trim( get_option( 'upload_path' ) ); - if ( empty( $upload_path ) || 'wp-content/uploads' == $upload_path ) { + if ( empty( $upload_path ) || 'wp-content/uploads' === $upload_path ) { $dir = WP_CONTENT_DIR . '/uploads'; } elseif ( 0 !== strpos( $upload_path, ABSPATH ) ) { - // $dir is absolute, $upload_path is (maybe) relative to ABSPATH + // $dir is absolute, $upload_path is (maybe) relative to ABSPATH. $dir = path_join( ABSPATH, $upload_path ); } else { $dir = $upload_path; } - if ( ! $url = get_option( 'upload_url_path' ) ) { - if ( empty( $upload_path ) || ( 'wp-content/uploads' == $upload_path ) || ( $upload_path == $dir ) ) { + $url = get_option( 'upload_url_path' ); + if ( ! $url ) { + if ( empty( $upload_path ) || ( 'wp-content/uploads' === $upload_path ) || ( $upload_path == $dir ) ) { $url = WP_CONTENT_URL . '/uploads'; } else { $url = trailingslashit( $siteurl ) . $upload_path; @@ -2165,7 +2372,7 @@ $url = trailingslashit( $siteurl ) . UPLOADS; } - // If multisite (and if not the main site in a post-MU network) + // If multisite (and if not the main site in a post-MU network). if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) { if ( ! get_site_option( 'ms_files_rewriting' ) ) { @@ -2216,7 +2423,7 @@ $subdir = ''; if ( get_option( 'uploads_use_yearmonth_folders' ) ) { - // Generate the yearly and monthly dirs + // Generate the yearly and monthly directories. if ( ! $time ) { $time = current_time( 'mysql' ); } @@ -2242,11 +2449,12 @@ * Get a filename that is sanitized and unique for the given directory. * * If the filename is not unique, then a number will be added to the filename - * before the extension, and will continue adding numbers until the filename is - * unique. - * - * The callback is passed three parameters, the first one is the directory, the - * second is the filename, and the third is the extension. + * before the extension, and will continue adding numbers until the filename + * is unique. + * + * The callback function allows the caller to use their own method to create + * unique file names. If defined, the callback should take three arguments: + * - directory, base filename, and extension - and return a unique filename. * * @since 2.5.0 * @@ -2258,10 +2466,12 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) { // Sanitize the file name before we begin processing. $filename = sanitize_file_name( $filename ); + $ext2 = null; // Separate the filename into a name and extension. $ext = pathinfo( $filename, PATHINFO_EXTENSION ); $name = pathinfo( $filename, PATHINFO_BASENAME ); + if ( $ext ) { $ext = '.' . $ext; } @@ -2279,6 +2489,15 @@ $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext ); } else { $number = ''; + $fname = pathinfo( $filename, PATHINFO_FILENAME ); + + // Always append a number to file names that can potentially match image sub-size file names. + if ( $fname && preg_match( '/-(?:\d+x\d+|scaled|rotated)$/', $fname ) ) { + $number = 1; + + // At this point the file name may not be unique. This is tested below and the $number is incremented. + $filename = str_replace( "{$fname}{$ext}", "{$fname}-{$number}{$ext}", $filename ); + } // Change '.ext' to lower case. if ( $ext && strtolower( $ext ) != $ext ) { @@ -2286,42 +2505,126 @@ $filename2 = preg_replace( '|' . preg_quote( $ext ) . '$|', $ext2, $filename ); // Check for both lower and upper case extension or image sub-sizes may be overwritten. - while ( file_exists( $dir . "/$filename" ) || file_exists( $dir . "/$filename2" ) ) { + while ( file_exists( $dir . "/{$filename}" ) || file_exists( $dir . "/{$filename2}" ) ) { $new_number = (int) $number + 1; - $filename = str_replace( array( "-$number$ext", "$number$ext" ), "-$new_number$ext", $filename ); - $filename2 = str_replace( array( "-$number$ext2", "$number$ext2" ), "-$new_number$ext2", $filename2 ); + $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename ); + $filename2 = str_replace( array( "-{$number}{$ext2}", "{$number}{$ext2}" ), "-{$new_number}{$ext2}", $filename2 ); $number = $new_number; } - /** - * Filters the result when generating a unique file name. - * - * @since 4.5.0 - * - * @param string $filename Unique file name. - * @param string $ext File extension, eg. ".png". - * @param string $dir Directory path. - * @param callable|null $unique_filename_callback Callback function that generates the unique file name. - */ - return apply_filters( 'wp_unique_filename', $filename2, $ext, $dir, $unique_filename_callback ); + $filename = $filename2; + } else { + while ( file_exists( $dir . "/{$filename}" ) ) { + $new_number = (int) $number + 1; + + if ( '' === "{$number}{$ext}" ) { + $filename = "{$filename}-{$new_number}"; + } else { + $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename ); + } + + $number = $new_number; + } } - while ( file_exists( $dir . "/$filename" ) ) { - $new_number = (int) $number + 1; - if ( '' == "$number$ext" ) { - $filename = "$filename-" . $new_number; - } else { - $filename = str_replace( array( "-$number$ext", "$number$ext" ), '-' . $new_number . $ext, $filename ); + // Prevent collisions with existing file names that contain dimension-like strings + // (whether they are subsizes or originals uploaded prior to #42437). + $upload_dir = wp_get_upload_dir(); + + // The (resized) image files would have name and extension, and will be in the uploads dir. + if ( $name && $ext && @is_dir( $dir ) && false !== strpos( $dir, $upload_dir['basedir'] ) ) { + /** + * Filters the file list used for calculating a unique filename for a newly added file. + * + * Returning an array from the filter will effectively short-circuit retrieval + * from the filesystem and return the passed value instead. + * + * @since 5.5.0 + * + * @param array|null $files The list of files to use for filename comparisons. + * Default null (to retrieve the list from the filesystem). + * @param string $dir The directory for the new file. + * @param string $filename The proposed filename for the new file. + */ + $files = apply_filters( 'pre_wp_unique_filename_file_list', null, $dir, $filename ); + + if ( null === $files ) { + // List of all files and directories contained in $dir. + $files = @scandir( $dir ); + } + + if ( ! empty( $files ) ) { + // Remove "dot" dirs. + $files = array_diff( $files, array( '.', '..' ) ); + } + + if ( ! empty( $files ) ) { + // The extension case may have changed above. + $new_ext = ! empty( $ext2 ) ? $ext2 : $ext; + + // Ensure this never goes into infinite loop + // as it uses pathinfo() and regex in the check, but string replacement for the changes. + $count = count( $files ); + $i = 0; + + while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) { + $new_number = (int) $number + 1; + $filename = str_replace( array( "-{$number}{$new_ext}", "{$number}{$new_ext}" ), "-{$new_number}{$new_ext}", $filename ); + $number = $new_number; + $i++; + } } - $number = $new_number; } } - /** This filter is documented in wp-includes/functions.php */ + /** + * Filters the result when generating a unique file name. + * + * @since 4.5.0 + * + * @param string $filename Unique file name. + * @param string $ext File extension, eg. ".png". + * @param string $dir Directory path. + * @param callable|null $unique_filename_callback Callback function that generates the unique file name. + */ return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback ); } /** + * Helper function to check if a file name could match an existing image sub-size file name. + * + * @since 5.3.1 + * @access private + * + * @param string $filename The file name to check. + * @param array $files An array of existing files in the directory. + * @return bool True if the tested file name could match an existing file, false otherwise. + */ +function _wp_check_existing_file_names( $filename, $files ) { + $fname = pathinfo( $filename, PATHINFO_FILENAME ); + $ext = pathinfo( $filename, PATHINFO_EXTENSION ); + + // Edge case, file names like `.ext`. + if ( empty( $fname ) ) { + return false; + } + + if ( $ext ) { + $ext = ".$ext"; + } + + $regex = '/^' . preg_quote( $fname ) . '-(?:\d+x\d+|scaled|rotated)' . preg_quote( $ext ) . '$/i'; + + foreach ( $files as $file ) { + if ( preg_match( $regex, $file ) ) { + return true; + } + } + + return false; +} + +/** * Create a file in the upload folder with given content. * * If there is an error, then the key 'error' will exist with the error message. @@ -2338,11 +2641,18 @@ * * @since 2.0.0 * - * @param string $name Filename. - * @param null|string $deprecated Never used. Set to null. - * @param mixed $bits File content - * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. - * @return array + * @param string $name Filename. + * @param null|string $deprecated Never used. Set to null. + * @param string $bits File content + * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. + * @return array { + * Information about the newly-uploaded file. + * + * @type string $file Filename of the newly-uploaded file. + * @type string $url URL of the uploaded file. + * @type string $type File type. + * @type string|false $error Error message, if there has been an error. + * } */ function wp_upload_bits( $name, $deprecated, $bits, $time = null ) { if ( ! empty( $deprecated ) ) { @@ -2360,19 +2670,19 @@ $upload = wp_upload_dir( $time ); - if ( $upload['error'] !== false ) { + if ( false !== $upload['error'] ) { return $upload; } /** * Filters whether to treat the upload bits as an error. * - * Passing a non-array to the filter will effectively short-circuit preparing - * the upload bits, returning that value instead. + * Returning a non-array from the filter will effectively short-circuit preparing the upload bits + * and return that value instead. An error message should be returned as a string. * * @since 3.0.0 * - * @param mixed $upload_bits_error An array of upload bits data, or a non-array error to return. + * @param array|string $upload_bits_error An array of upload bits data, or error message to return. */ $upload_bits_error = apply_filters( 'wp_upload_bits', @@ -2398,30 +2708,33 @@ } $message = sprintf( - /* translators: %s: directory path */ + /* translators: %s: Directory path. */ __( 'Unable to create directory %s. Is its parent directory writable by the server?' ), $error_path ); return array( 'error' => $message ); } - $ifp = @ fopen( $new_file, 'wb' ); + $ifp = @fopen( $new_file, 'wb' ); if ( ! $ifp ) { - return array( 'error' => sprintf( __( 'Could not write file %s' ), $new_file ) ); - } - - @fwrite( $ifp, $bits ); + return array( + /* translators: %s: File name. */ + 'error' => sprintf( __( 'Could not write file %s' ), $new_file ), + ); + } + + fwrite( $ifp, $bits ); fclose( $ifp ); clearstatcache(); - // Set correct file permissions + // Set correct file permissions. $stat = @ stat( dirname( $new_file ) ); $perms = $stat['mode'] & 0007777; $perms = $perms & 0000666; - @ chmod( $new_file, $perms ); + chmod( $new_file, $perms ); clearstatcache(); - // Compute the URL + // Compute the URL. $url = $upload['url'] . "/$filename"; /** This filter is documented in wp-admin/includes/file.php */ @@ -2450,7 +2763,7 @@ $ext2type = wp_get_ext_types(); foreach ( $ext2type as $type => $exts ) { - if ( in_array( $ext, $exts ) ) { + if ( in_array( $ext, $exts, true ) ) { return $type; } } @@ -2463,9 +2776,14 @@ * * @since 2.0.4 * - * @param string $filename File name or path. - * @param array $mimes Optional. Key is the file extension with value as the mime type. - * @return array Values with extension first and mime type. + * @param string $filename File name or path. + * @param string[] $mimes Optional. Array of mime types keyed by their file extension regex. + * @return array { + * Values for the extension and mime type. + * + * @type string|false $ext File extension, or false if the file doesn't match a mime type. + * @type string|false $type File mime type, or false if the file doesn't match a mime type. + * } */ function wp_check_filetype( $filename, $mimes = null ) { if ( empty( $mimes ) ) { @@ -2498,22 +2816,27 @@ * * @since 3.0.0 * - * @param string $file Full path to the file. - * @param string $filename The name of the file (may differ from $file due to $file being - * in a tmp directory). - * @param array $mimes Optional. Key is the file extension with value as the mime type. - * @return array Values for the extension, MIME, and either a corrected filename or false - * if original $filename is valid. + * @param string $file Full path to the file. + * @param string $filename The name of the file (may differ from $file due to $file being + * in a tmp directory). + * @param string[] $mimes Optional. Array of mime types keyed by their file extension regex. + * @return array { + * Values for the extension, mime type, and corrected filename. + * + * @type string|false $ext File extension, or false if the file doesn't match a mime type. + * @type string|false $type File mime type, or false if the file doesn't match a mime type. + * @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined. + * } */ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { $proper_filename = false; - // Do basic extension validation and MIME mapping + // Do basic extension validation and MIME mapping. $wp_filetype = wp_check_filetype( $filename, $mimes ); $ext = $wp_filetype['ext']; $type = $wp_filetype['type']; - // We can't do any further validation without a file to work with + // We can't do any further validation without a file to work with. if ( ! file_exists( $file ) ) { return compact( 'ext', 'type', 'proper_filename' ); } @@ -2523,7 +2846,7 @@ // Validate image types. if ( $type && 0 === strpos( $type, 'image/' ) ) { - // Attempt to figure out what type of image it actually is + // Attempt to figure out what type of image it actually is. $real_mime = wp_get_image_mime( $file ); if ( $real_mime && $real_mime != $type ) { @@ -2532,7 +2855,7 @@ * * @since 3.0.0 * - * @param array $mime_to_ext Array of image mime types and their matching extensions. + * @param array $mime_to_ext Array of image mime types and their matching extensions. */ $mime_to_ext = apply_filters( 'getimagesize_mimes_to_exts', @@ -2545,7 +2868,7 @@ ) ); - // Replace whatever is after the last period in the filename with the correct extension + // Replace whatever is after the last period in the filename with the correct extension. if ( ! empty( $mime_to_ext[ $real_mime ] ) ) { $filename_parts = explode( '.', $filename ); array_pop( $filename_parts ); @@ -2553,9 +2876,9 @@ $new_filename = implode( '.', $filename_parts ); if ( $new_filename != $filename ) { - $proper_filename = $new_filename; // Mark that it changed + $proper_filename = $new_filename; // Mark that it changed. } - // Redefine the extension / MIME + // Redefine the extension / MIME. $wp_filetype = wp_check_filetype( $new_filename, $mimes ); $ext = $wp_filetype['ext']; $type = $wp_filetype['type']; @@ -2572,7 +2895,7 @@ $real_mime = finfo_file( $finfo, $file ); finfo_close( $finfo ); - // fileinfo often misidentifies obscure files as one of these types + // fileinfo often misidentifies obscure files as one of these types. $nonspecific_types = array( 'application/octet-stream', 'application/encrypted', @@ -2587,8 +2910,9 @@ */ if ( in_array( $real_mime, $nonspecific_types, true ) ) { // File is a non-specific binary type. That's ok if it's a type that generally tends to be binary. - if ( ! in_array( substr( $type, 0, strcspn( $type, '/' ) ), array( 'application', 'video', 'audio' ) ) ) { - $type = $ext = false; + if ( ! in_array( substr( $type, 0, strcspn( $type, '/' ) ), array( 'application', 'video', 'audio' ), true ) ) { + $type = false; + $ext = false; } } elseif ( 0 === strpos( $real_mime, 'video/' ) || 0 === strpos( $real_mime, 'audio/' ) ) { /* @@ -2597,7 +2921,8 @@ * and some media files are commonly named with the wrong extension (.mov instead of .mp4) */ if ( substr( $real_mime, 0, strcspn( $real_mime, '/' ) ) !== substr( $type, 0, strcspn( $type, '/' ) ) ) { - $type = $ext = false; + $type = false; + $ext = false; } } elseif ( 'text/plain' === $real_mime ) { // A few common file types are occasionally detected as text/plain; allow those. @@ -2609,10 +2934,12 @@ 'text/richtext', 'text/tsv', 'text/vtt', - ) + ), + true ) ) { - $type = $ext = false; + $type = false; + $ext = false; } } elseif ( 'text/rtf' === $real_mime ) { // Special casing for RTF files. @@ -2622,10 +2949,12 @@ 'text/rtf', 'text/plain', 'application/rtf', - ) + ), + true ) ) { - $type = $ext = false; + $type = false; + $ext = false; } } else { if ( $type !== $real_mime ) { @@ -2633,17 +2962,19 @@ * Everything else including image/* and application/*: * If the real content type doesn't match the file extension, assume it's dangerous. */ - $type = $ext = false; + $type = false; + $ext = false; } } } - // The mime type must be allowed + // The mime type must be allowed. if ( $type ) { $allowed = get_allowed_mime_types(); - if ( ! in_array( $type, $allowed ) ) { - $type = $ext = false; + if ( ! in_array( $type, $allowed, true ) ) { + $type = false; + $ext = false; } } @@ -2653,12 +2984,17 @@ * @since 3.0.0 * @since 5.1.0 The $real_mime parameter was added. * - * @param array $wp_check_filetype_and_ext File data array containing 'ext', 'type', and - * 'proper_filename' keys. + * @param array $wp_check_filetype_and_ext { + * Values for the extension, mime type, and corrected filename. + * + * @type string|false $ext File extension, or false if the file doesn't match a mime type. + * @type string|false $type File mime type, or false if the file doesn't match a mime type. + * @type string|false $proper_filename File name with its correct extension, or false if it cannot be determined. + * } * @param string $file Full path to the file. * @param string $filename The name of the file (may differ from $file due to * $file being in a tmp directory). - * @param array $mimes Key is the file extension with value as the mime type. + * @param string[] $mimes Array of mime types keyed by their file extension regex. * @param string|bool $real_mime The actual mime type or false if the type cannot be determined. */ return apply_filters( 'wp_check_filetype_and_ext', compact( 'ext', 'type', 'proper_filename' ), $file, $filename, $mimes, $real_mime ); @@ -2685,7 +3021,7 @@ $imagetype = exif_imagetype( $file ); $mime = ( $imagetype ) ? image_type_to_mime_type( $imagetype ) : false; } elseif ( function_exists( 'getimagesize' ) ) { - $imagesize = getimagesize( $file ); + $imagesize = @getimagesize( $file ); $mime = ( isset( $imagesize['mime'] ) ) ? $imagesize['mime'] : false; } else { $mime = false; @@ -2701,9 +3037,11 @@ * Retrieve list of mime types and file extensions. * * @since 3.5.0 - * @since 4.2.0 Support was added for GIMP (xcf) files. - * - * @return array Array of mime types keyed by the file extension regex corresponding to those types. + * @since 4.2.0 Support was added for GIMP (.xcf) files. + * @since 4.9.2 Support was added for Flac (.flac) files. + * @since 4.9.6 Support was added for AAC (.aac) files. + * + * @return string[] Array of mime types keyed by the file extension regex corresponding to those types. */ function wp_get_mime_types() { /** @@ -2714,7 +3052,7 @@ * * @since 3.5.0 * - * @param array $wp_get_mime_types Mime types keyed by the file extension regex + * @param string[] $wp_get_mime_types Mime types keyed by the file extension regex * corresponding to those types. */ return apply_filters( @@ -2727,6 +3065,7 @@ 'bmp' => 'image/bmp', 'tiff|tif' => 'image/tiff', 'ico' => 'image/x-icon', + 'heic' => 'image/heic', // Video formats. 'asf|asx' => 'video/x-ms-asf', 'wmv' => 'video/x-ms-wmv', @@ -2741,8 +3080,8 @@ 'ogv' => 'video/ogg', 'webm' => 'video/webm', 'mkv' => 'video/x-matroska', - '3gp|3gpp' => 'video/3gpp', // Can also be audio - '3g2|3gp2' => 'video/3gpp2', // Can also be audio + '3gp|3gpp' => 'video/3gpp', // Can also be audio. + '3g2|3gp2' => 'video/3gpp2', // Can also be audio. // Text formats. 'txt|asc|c|cc|h|srt' => 'text/plain', 'csv' => 'text/csv', @@ -2830,7 +3169,7 @@ * * @since 4.6.0 * - * @return array Array of file extensions types keyed by the type of file. + * @return array[] Multi-dimensional array of file extensions types keyed by the type of file. */ function wp_get_ext_types() { @@ -2841,13 +3180,12 @@ * * @see wp_ext2type() * - * @param array $ext2type Multi-dimensional array with extensions for a default set - * of file types. + * @param array[] $ext2type Multi-dimensional array of file extensions types keyed by the type of file. */ return apply_filters( 'ext2type', array( - 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico' ), + 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic' ), 'audio' => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ), 'video' => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ), 'document' => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ), @@ -2866,8 +3204,8 @@ * @since 2.8.6 * * @param int|WP_User $user Optional. User to check. Defaults to current user. - * @return array Array of mime types keyed by the file extension regex corresponding - * to those types. + * @return string[] Array of mime types keyed by the file extension regex corresponding + * to those types. */ function get_allowed_mime_types( $user = null ) { $t = wp_get_mime_types(); @@ -2886,9 +3224,7 @@ * * @since 2.0.0 * - * @param array $t Mime types keyed by the file extension regex corresponding to - * those types. 'swf' and 'exe' removed from full list. 'htm|html' also - * removed depending on '$user' capabilities. + * @param array $t Mime types keyed by the file extension regex corresponding to those types. * @param int|WP_User|null $user User ID, User object or null if not provided (indicates current user). */ return apply_filters( 'upload_mimes', $t, $user ); @@ -2905,16 +3241,16 @@ * @param string $action The nonce action. */ function wp_nonce_ays( $action ) { - if ( 'log-out' == $action ) { + if ( 'log-out' === $action ) { $html = sprintf( - /* translators: %s: site name */ + /* translators: %s: Site title. */ __( 'You are attempting to log out of %s' ), get_bloginfo( 'name' ) ); $html .= '

'; $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : ''; $html .= sprintf( - /* translators: %s: logout URL */ + /* translators: %s: Logout URL. */ __( 'Do you really want to log out?' ), wp_logout_url( $redirect_to ) ); @@ -2949,8 +3285,11 @@ * @since 4.1.0 The `$title` and `$args` parameters were changed to optionally accept * an integer to be used as the response code. * @since 5.1.0 The `$link_url`, `$link_text`, and `$exit` arguments were added. - * - * @global WP_Query $wp_query Global WP_Query instance. + * @since 5.3.0 The `$charset` argument was added. + * @since 5.5.0 The `$text_direction` argument has a priority over get_language_attributes() + * in the default handler. + * + * @global WP_Query $wp_query WordPress Query object. * * @param string|WP_Error $message Optional. Error message. If this is a WP_Error object, * and not an Ajax or XML-RPC request, the error's messages are used. @@ -2969,9 +3308,10 @@ * @type string $link_text A label for the link to include. Only works in combination with $link_url. * Default empty string. * @type bool $back_link Whether to include a link to go back. Default false. - * @type string $text_direction The text direction. This is only useful internally, when WordPress - * is still loading and the site's locale is not set up yet. Accepts 'rtl'. + * @type string $text_direction The text direction. This is only useful internally, when WordPress is still + * loading and the site's locale is not set up yet. Accepts 'rtl' and 'ltr'. * Default is the value of is_rtl(). + * @type string $charset Character set of the HTML output. Default 'utf-8'. * @type string $code Error code to use. Default is 'wp_die', or the main error code if $message * is a WP_Error. * @type bool $exit Whether to exit the process after completion. Default true. @@ -3064,54 +3404,61 @@ * @param string|array $args Optional. Arguments to control behavior. Default empty array. */ function _default_wp_die_handler( $message, $title = '', $args = array() ) { - list( $message, $title, $r ) = _wp_die_process_input( $message, $title, $args ); + list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args ); if ( is_string( $message ) ) { - if ( ! empty( $r['additional_errors'] ) ) { + if ( ! empty( $parsed_args['additional_errors'] ) ) { $message = array_merge( array( $message ), - wp_list_pluck( $r['additional_errors'], 'message' ) + wp_list_pluck( $parsed_args['additional_errors'], 'message' ) ); $message = "

"; - } else { - $message = "

$message

"; } + + $message = sprintf( + '
%s
', + $message + ); } $have_gettext = function_exists( '__' ); - if ( ! empty( $r['link_url'] ) && ! empty( $r['link_text'] ) ) { - $link_url = $r['link_url']; + if ( ! empty( $parsed_args['link_url'] ) && ! empty( $parsed_args['link_text'] ) ) { + $link_url = $parsed_args['link_url']; if ( function_exists( 'esc_url' ) ) { $link_url = esc_url( $link_url ); } - $link_text = $r['link_text']; + $link_text = $parsed_args['link_text']; $message .= "\n

{$link_text}

"; } - if ( isset( $r['back_link'] ) && $r['back_link'] ) { + if ( isset( $parsed_args['back_link'] ) && $parsed_args['back_link'] ) { $back_text = $have_gettext ? __( '« Back' ) : '« Back'; $message .= "\n

$back_text

"; } if ( ! did_action( 'admin_head' ) ) : if ( ! headers_sent() ) { - header( 'Content-Type: text/html; charset=utf-8' ); - status_header( $r['response'] ); + header( "Content-Type: text/html; charset={$parsed_args['charset']}" ); + status_header( $parsed_args['response'] ); nocache_headers(); } - $text_direction = $r['text_direction']; - if ( function_exists( 'language_attributes' ) && function_exists( 'is_rtl' ) ) { + $text_direction = $parsed_args['text_direction']; + $dir_attr = "dir='$text_direction'"; + + // If `text_direction` was not explicitly passed, + // use get_language_attributes() if available. + if ( empty( $args['text_direction'] ) + && function_exists( 'language_attributes' ) && function_exists( 'is_rtl' ) + ) { $dir_attr = get_language_attributes(); - } else { - $dir_attr = "dir='$text_direction'"; } ?> -> +> - + @@ -3242,7 +3587,7 @@ 200 ) ); - list( $message, $title, $r ) = _wp_die_process_input( $message, $title, $args ); + list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args ); if ( ! headers_sent() ) { // This is intentional. For backward-compatibility, support passing null here. if ( null !== $args['response'] ) { - status_header( $r['response'] ); + status_header( $parsed_args['response'] ); } nocache_headers(); } @@ -3282,7 +3627,7 @@ $message = '0'; } - if ( $r['exit'] ) { + if ( $parsed_args['exit'] ) { die( $message ); } @@ -3302,27 +3647,27 @@ * @param string|array $args Optional. Arguments to control behavior. Default empty array. */ function _json_wp_die_handler( $message, $title = '', $args = array() ) { - list( $message, $title, $r ) = _wp_die_process_input( $message, $title, $args ); + list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args ); $data = array( - 'code' => $r['code'], + 'code' => $parsed_args['code'], 'message' => $message, 'data' => array( - 'status' => $r['response'], + 'status' => $parsed_args['response'], ), - 'additional_errors' => $r['additional_errors'], + 'additional_errors' => $parsed_args['additional_errors'], ); if ( ! headers_sent() ) { - header( 'Content-Type: application/json; charset=utf-8' ); - if ( null !== $r['response'] ) { - status_header( $r['response'] ); + header( "Content-Type: application/json; charset={$parsed_args['charset']}" ); + if ( null !== $parsed_args['response'] ) { + status_header( $parsed_args['response'] ); } nocache_headers(); } echo wp_json_encode( $data ); - if ( $r['exit'] ) { + if ( $parsed_args['exit'] ) { die(); } } @@ -3340,23 +3685,23 @@ * @param string|array $args Optional. Arguments to control behavior. Default empty array. */ function _jsonp_wp_die_handler( $message, $title = '', $args = array() ) { - list( $message, $title, $r ) = _wp_die_process_input( $message, $title, $args ); + list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args ); $data = array( - 'code' => $r['code'], + 'code' => $parsed_args['code'], 'message' => $message, 'data' => array( - 'status' => $r['response'], + 'status' => $parsed_args['response'], ), - 'additional_errors' => $r['additional_errors'], + 'additional_errors' => $parsed_args['additional_errors'], ); if ( ! headers_sent() ) { - header( 'Content-Type: application/javascript; charset=utf-8' ); + header( "Content-Type: application/javascript; charset={$parsed_args['charset']}" ); header( 'X-Content-Type-Options: nosniff' ); header( 'X-Robots-Tag: noindex' ); - if ( null !== $r['response'] ) { - status_header( $r['response'] ); + if ( null !== $parsed_args['response'] ) { + status_header( $parsed_args['response'] ); } nocache_headers(); } @@ -3364,7 +3709,7 @@ $result = wp_json_encode( $data ); $jsonp_callback = $_GET['_jsonp']; echo '/**/' . $jsonp_callback . '(' . $result . ')'; - if ( $r['exit'] ) { + if ( $parsed_args['exit'] ) { die(); } } @@ -3386,17 +3731,17 @@ function _xmlrpc_wp_die_handler( $message, $title = '', $args = array() ) { global $wp_xmlrpc_server; - list( $message, $title, $r ) = _wp_die_process_input( $message, $title, $args ); + list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args ); if ( ! headers_sent() ) { nocache_headers(); } if ( $wp_xmlrpc_server ) { - $error = new IXR_Error( $r['response'], $message ); + $error = new IXR_Error( $parsed_args['response'], $message ); $wp_xmlrpc_server->output( $error->getXml() ); } - if ( $r['exit'] ) { + if ( $parsed_args['exit'] ) { die(); } } @@ -3414,33 +3759,33 @@ * @param string|array $args Optional. Arguments to control behavior. Default empty array. */ function _xml_wp_die_handler( $message, $title = '', $args = array() ) { - list( $message, $title, $r ) = _wp_die_process_input( $message, $title, $args ); + list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args ); $message = htmlspecialchars( $message ); $title = htmlspecialchars( $title ); $xml = << - {$r['code']} + {$parsed_args['code']} <![CDATA[{$title}]]> - {$r['response']} + {$parsed_args['response']} EOD; if ( ! headers_sent() ) { - header( 'Content-Type: text/xml; charset=utf-8' ); - if ( null !== $r['response'] ) { - status_header( $r['response'] ); + header( "Content-Type: text/xml; charset={$parsed_args['charset']}" ); + if ( null !== $parsed_args['response'] ) { + status_header( $parsed_args['response'] ); } nocache_headers(); } echo $xml; - if ( $r['exit'] ) { + if ( $parsed_args['exit'] ) { die(); } } @@ -3459,9 +3804,9 @@ * @param string|array $args Optional. Arguments to control behavior. Default empty array. */ function _scalar_wp_die_handler( $message = '', $title = '', $args = array() ) { - list( $message, $title, $r ) = _wp_die_process_input( $message, $title, $args ); - - if ( $r['exit'] ) { + list( $message, $title, $parsed_args ) = _wp_die_process_input( $message, $title, $args ); + + if ( $parsed_args['exit'] ) { if ( is_scalar( $message ) ) { die( (string) $message ); } @@ -3479,10 +3824,16 @@ * @since 5.1.0 * @access private * - * @param string $message Error message. - * @param string $title Optional. Error title. Default empty. - * @param string|array $args Optional. Arguments to control behavior. Default empty array. - * @return array List of processed $message string, $title string, and $args array. + * @param string|WP_Error $message Error message or WP_Error object. + * @param string $title Optional. Error title. Default empty. + * @param string|array $args Optional. Arguments to control behavior. Default empty array. + * @return array { + * Processed arguments. + * + * @type string $0 Error message. + * @type string $1 Error title. + * @type array $2 Arguments to control behavior. + * } */ function _wp_die_process_input( $message, $title = '', $args = array() ) { $defaults = array( @@ -3493,6 +3844,7 @@ 'link_url' => '', 'link_text' => '', 'text_direction' => '', + 'charset' => 'utf-8', 'additional_errors' => array(), ); @@ -3548,6 +3900,10 @@ } } + if ( ! empty( $args['charset'] ) ) { + $args['charset'] = _canonical_charset( $args['charset'] ); + } + return array( $message, $title, $args ); } @@ -3555,6 +3911,7 @@ * Encode a variable into JSON, with some sanity checks. * * @since 4.1.0 + * @since 5.3.0 No longer handles support for PHP < 5.6. * * @param mixed $data Variable (usually an array or object) to encode as JSON. * @param int $options Optional. Options to be passed to json_encode(). Default 0. @@ -3563,38 +3920,20 @@ * @return string|false The JSON encoded string, or false if it cannot be encoded. */ function wp_json_encode( $data, $options = 0, $depth = 512 ) { - /* - * json_encode() has had extra params added over the years. - * $options was added in 5.3, and $depth in 5.5. - * We need to make sure we call it with the correct arguments. - */ - if ( version_compare( PHP_VERSION, '5.5', '>=' ) ) { - $args = array( $data, $options, $depth ); - } elseif ( version_compare( PHP_VERSION, '5.3', '>=' ) ) { - $args = array( $data, $options ); - } else { - $args = array( $data ); - } - - // Prepare the data for JSON serialization. - $args[0] = _wp_json_prepare_data( $data ); - - $json = @call_user_func_array( 'json_encode', $args ); + $json = json_encode( $data, $options, $depth ); // If json_encode() was successful, no need to do more sanity checking. - // ... unless we're in an old version of PHP, and json_encode() returned - // a string containing 'null'. Then we need to do more sanity checking. - if ( false !== $json && ( version_compare( PHP_VERSION, '5.5', '>=' ) || false === strpos( $json, 'null' ) ) ) { + if ( false !== $json ) { return $json; } try { - $args[0] = _wp_json_sanity_check( $data, $depth ); + $data = _wp_json_sanity_check( $data, $depth ); } catch ( Exception $e ) { return false; } - return call_user_func_array( 'json_encode', $args ); + return json_encode( $data, $options, $depth ); } /** @@ -3606,6 +3945,8 @@ * * @see wp_json_encode() * + * @throws Exception If depth limit is reached. + * * @param mixed $data Variable (usually an array or object) to encode as JSON. * @param int $depth Maximum depth to walk through $data. Must be greater than 0. * @return mixed The sanitized data that shall be encoded to JSON. @@ -3669,8 +4010,6 @@ * * @see _wp_json_sanity_check() * - * @staticvar bool $use_mb - * * @param string $string The string which is to be converted. * @return string The checked string. */ @@ -3699,47 +4038,16 @@ * * @ignore * @since 4.4.0 + * @deprecated 5.3.0 This function is no longer needed as support for PHP 5.2-5.3 + * has been dropped. * @access private * * @param mixed $data Native representation. * @return bool|int|float|null|string|array Data ready for `json_encode()`. */ function _wp_json_prepare_data( $data ) { - if ( ! defined( 'WP_JSON_SERIALIZE_COMPATIBLE' ) || WP_JSON_SERIALIZE_COMPATIBLE === false ) { - return $data; - } - - switch ( gettype( $data ) ) { - case 'boolean': - case 'integer': - case 'double': - case 'string': - case 'NULL': - // These values can be passed through. - return $data; - - case 'array': - // Arrays must be mapped in case they also return objects. - return array_map( '_wp_json_prepare_data', $data ); - - case 'object': - // If this is an incomplete object (__PHP_Incomplete_Class), bail. - if ( ! is_object( $data ) ) { - return null; - } - - if ( $data instanceof JsonSerializable ) { - $data = $data->jsonSerialize(); - } else { - $data = get_object_vars( $data ); - } - - // Now, pass the array (or whatever was returned from jsonSerialize through). - return _wp_json_prepare_data( $data ); - - default: - return null; - } + _deprecated_function( __FUNCTION__, '5.3.0' ); + return $data; } /** @@ -3753,10 +4061,26 @@ * @param int $status_code The HTTP status code to output. */ function wp_send_json( $response, $status_code = null ) { - @header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) ); - if ( null !== $status_code ) { - status_header( $status_code ); - } + if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: 1: WP_REST_Response, 2: WP_Error */ + __( 'Return a %1$s or %2$s object from your callback when using the REST API.' ), + 'WP_REST_Response', + 'WP_Error' + ), + '5.5.0' + ); + } + + if ( ! headers_sent() ) { + header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) ); + if ( null !== $status_code ) { + status_header( $status_code ); + } + } + echo wp_json_encode( $response ); if ( wp_doing_ajax() ) { @@ -3831,7 +4155,7 @@ } /** - * Checks that a JSONP callback is a valid JavaScript callback. + * Checks that a JSONP callback is a valid JavaScript callback name. * * Only allows alphanumeric characters and the dot character in callback * function names. This helps to mitigate XSS attacks caused by directly @@ -3839,8 +4163,8 @@ * * @since 4.6.0 * - * @param string $callback Supplied JSONP callback function. - * @return bool True if valid callback, otherwise false. + * @param string $callback Supplied JSONP callback function name. + * @return bool Whether the callback function name is valid. */ function wp_check_jsonp_callback( $callback ) { if ( ! is_string( $callback ) ) { @@ -3966,7 +4290,7 @@ function smilies_init() { global $wpsmiliestrans, $wp_smiliessearch; - // don't bother setting up smilies if they are disabled + // Don't bother setting up smilies if they are disabled. if ( ! get_option( 'use_smilies' ) ) { return; } @@ -4029,7 +4353,7 @@ * * @since 4.7.0 * - * @param array $wpsmiliestrans List of the smilies. + * @param string[] $wpsmiliestrans List of the smilies' hexadecimal representations, keyed by their smily code. */ $wpsmiliestrans = apply_filters( 'smilies', $wpsmiliestrans ); @@ -4046,7 +4370,7 @@ $spaces = wp_spaces_regexp(); - // Begin first "subpattern" + // Begin first "subpattern". $wp_smiliessearch = '/(?<=' . $spaces . '|^)'; $subchar = ''; @@ -4054,11 +4378,11 @@ $firstchar = substr( $smiley, 0, 1 ); $rest = substr( $smiley, 1 ); - // new subpattern? + // New subpattern? if ( $firstchar != $subchar ) { - if ( $subchar != '' ) { - $wp_smiliessearch .= ')(?=' . $spaces . '|$)'; // End previous "subpattern" - $wp_smiliessearch .= '|(?<=' . $spaces . '|^)'; // Begin another "subpattern" + if ( '' !== $subchar ) { + $wp_smiliessearch .= ')(?=' . $spaces . '|$)'; // End previous "subpattern". + $wp_smiliessearch .= '|(?<=' . $spaces . '|^)'; // Begin another "subpattern". } $subchar = $firstchar; $wp_smiliessearch .= preg_quote( $firstchar, '/' ) . '(?:'; @@ -4082,22 +4406,23 @@ * @since 2.3.0 `$args` can now also be an object. * * @param string|array|object $args Value to merge with $defaults. - * @param array $defaults Optional. Array that serves as the defaults. Default empty. + * @param array $defaults Optional. Array that serves as the defaults. + * Default empty array. * @return array Merged user defined values with defaults. */ -function wp_parse_args( $args, $defaults = '' ) { +function wp_parse_args( $args, $defaults = array() ) { if ( is_object( $args ) ) { - $r = get_object_vars( $args ); + $parsed_args = get_object_vars( $args ); } elseif ( is_array( $args ) ) { - $r =& $args; + $parsed_args =& $args; } else { - wp_parse_str( $args, $r ); - } - - if ( is_array( $defaults ) ) { - return array_merge( $defaults, $r ); - } - return $r; + wp_parse_str( $args, $parsed_args ); + } + + if ( is_array( $defaults ) && $defaults ) { + return array_merge( $defaults, $parsed_args ); + } + return $parsed_args; } /** @@ -4121,8 +4446,8 @@ * * @since 3.0.0 * - * @param array|string $list List of ids. - * @return array Sanitized array of IDs. + * @param array|string $list List of IDs. + * @return int[] Sanitized array of IDs. */ function wp_parse_id_list( $list ) { $list = wp_parse_list( $list ); @@ -4135,8 +4460,8 @@ * * @since 4.7.0 * - * @param array|string $list List of slugs. - * @return array Sanitized array of slugs. + * @param array|string $list List of slugs. + * @return string[] Sanitized array of slugs. */ function wp_parse_slug_list( $list ) { $list = wp_parse_list( $list ); @@ -4296,7 +4621,7 @@ /** * Filters whether to load the Widgets library. * - * Passing a falsey value to the filter will effectively short-circuit + * Returning a falsey value from the filter will effectively short-circuit * the Widgets library from loading. * * @since 2.8.0 @@ -4308,7 +4633,7 @@ return; } - require_once( ABSPATH . WPINC . '/default-widgets.php' ); + require_once ABSPATH . WPINC . '/default-widgets.php'; add_action( '_admin_menu', 'wp_widgets_add_menu' ); } @@ -4370,7 +4695,7 @@ // Load custom DB error template, if present. if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) { - require_once( WP_CONTENT_DIR . '/db-error.php' ); + require_once WP_CONTENT_DIR . '/db-error.php'; die(); } @@ -4407,13 +4732,14 @@ * This function is to be used in every function that is deprecated. * * @since 2.5.0 - * @access private + * @since 5.4.0 This function is no longer marked as "private". + * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE). * * @param string $function The function that was called. * @param string $version The version of WordPress that deprecated the function. - * @param string $replacement Optional. The function that should have been called. Default null. - */ -function _deprecated_function( $function, $version, $replacement = null ) { + * @param string $replacement Optional. The function that should have been called. Default empty. + */ +function _deprecated_function( $function, $version, $replacement = '' ) { /** * Fires when a deprecated function is called. @@ -4435,18 +4761,48 @@ */ if ( WP_DEBUG && apply_filters( 'deprecated_function_trigger_error', true ) ) { if ( function_exists( '__' ) ) { - if ( ! is_null( $replacement ) ) { - /* translators: 1: PHP function name, 2: version number, 3: alternative function name */ - trigger_error( sprintf( __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), $function, $version, $replacement ) ); + if ( $replacement ) { + trigger_error( + sprintf( + /* translators: 1: PHP function name, 2: Version number, 3: Alternative function name. */ + __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), + $function, + $version, + $replacement + ), + E_USER_DEPRECATED + ); } else { - /* translators: 1: PHP function name, 2: version number */ - trigger_error( sprintf( __( '%1$s is deprecated since version %2$s with no alternative available.' ), $function, $version ) ); + trigger_error( + sprintf( + /* translators: 1: PHP function name, 2: Version number. */ + __( '%1$s is deprecated since version %2$s with no alternative available.' ), + $function, + $version + ), + E_USER_DEPRECATED + ); } } else { - if ( ! is_null( $replacement ) ) { - trigger_error( sprintf( '%1$s is deprecated since version %2$s! Use %3$s instead.', $function, $version, $replacement ) ); + if ( $replacement ) { + trigger_error( + sprintf( + '%1$s is deprecated since version %2$s! Use %3$s instead.', + $function, + $version, + $replacement + ), + E_USER_DEPRECATED + ); } else { - trigger_error( sprintf( '%1$s is deprecated since version %2$s with no alternative available.', $function, $version ) ); + trigger_error( + sprintf( + '%1$s is deprecated since version %2$s with no alternative available.', + $function, + $version + ), + E_USER_DEPRECATED + ); } } } @@ -4464,8 +4820,8 @@ * * @since 4.3.0 * @since 4.5.0 Added the `$parent_class` parameter. - * - * @access private + * @since 5.4.0 This function is no longer marked as "private". + * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE). * * @param string $class The class containing the deprecated constructor. * @param string $version The version of WordPress that deprecated the function. @@ -4497,38 +4853,41 @@ */ if ( WP_DEBUG && apply_filters( 'deprecated_constructor_trigger_error', true ) ) { if ( function_exists( '__' ) ) { - if ( ! empty( $parent_class ) ) { - /* translators: 1: PHP class name, 2: PHP parent class name, 3: version number, 4: __construct() method */ + if ( $parent_class ) { trigger_error( sprintf( + /* translators: 1: PHP class name, 2: PHP parent class name, 3: Version number, 4: __construct() method. */ __( 'The called constructor method for %1$s in %2$s is deprecated since version %3$s! Use %4$s instead.' ), $class, $parent_class, $version, - '
__construct()
' - ) + '__construct()' + ), + E_USER_DEPRECATED ); } else { - /* translators: 1: PHP class name, 2: version number, 3: __construct() method */ trigger_error( sprintf( + /* translators: 1: PHP class name, 2: Version number, 3: __construct() method. */ __( 'The called constructor method for %1$s is deprecated since version %2$s! Use %3$s instead.' ), $class, $version, - '
__construct()
' - ) + '__construct()' + ), + E_USER_DEPRECATED ); } } else { - if ( ! empty( $parent_class ) ) { + if ( $parent_class ) { trigger_error( sprintf( 'The called constructor method for %1$s in %2$s is deprecated since version %3$s! Use %4$s instead.', $class, $parent_class, $version, - '
__construct()
' - ) + '__construct()' + ), + E_USER_DEPRECATED ); } else { trigger_error( @@ -4536,8 +4895,9 @@ 'The called constructor method for %1$s is deprecated since version %2$s! Use %3$s instead.', $class, $version, - '
__construct()
' - ) + '__construct()' + ), + E_USER_DEPRECATED ); } } @@ -4557,15 +4917,16 @@ * This function is to be used in every file that is deprecated. * * @since 2.5.0 - * @access private + * @since 5.4.0 This function is no longer marked as "private". + * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE). * * @param string $file The file that was included. * @param string $version The version of WordPress that deprecated the file. * @param string $replacement Optional. The file that should have been included based on ABSPATH. - * Default null. + * Default empty. * @param string $message Optional. A message regarding the change. Default empty. */ -function _deprecated_file( $file, $version, $replacement = null, $message = '' ) { +function _deprecated_file( $file, $version, $replacement = '', $message = '' ) { /** * Fires when a deprecated file is called. @@ -4588,19 +4949,50 @@ */ if ( WP_DEBUG && apply_filters( 'deprecated_file_trigger_error', true ) ) { $message = empty( $message ) ? '' : ' ' . $message; + if ( function_exists( '__' ) ) { - if ( ! is_null( $replacement ) ) { - /* translators: 1: PHP file name, 2: version number, 3: alternative file name */ - trigger_error( sprintf( __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), $file, $version, $replacement ) . $message ); + if ( $replacement ) { + trigger_error( + sprintf( + /* translators: 1: PHP file name, 2: Version number, 3: Alternative file name. */ + __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), + $file, + $version, + $replacement + ) . $message, + E_USER_DEPRECATED + ); } else { - /* translators: 1: PHP file name, 2: version number */ - trigger_error( sprintf( __( '%1$s is deprecated since version %2$s with no alternative available.' ), $file, $version ) . $message ); + trigger_error( + sprintf( + /* translators: 1: PHP file name, 2: Version number. */ + __( '%1$s is deprecated since version %2$s with no alternative available.' ), + $file, + $version + ) . $message, + E_USER_DEPRECATED + ); } } else { - if ( ! is_null( $replacement ) ) { - trigger_error( sprintf( '%1$s is deprecated since version %2$s! Use %3$s instead.', $file, $version, $replacement ) . $message ); + if ( $replacement ) { + trigger_error( + sprintf( + '%1$s is deprecated since version %2$s! Use %3$s instead.', + $file, + $version, + $replacement + ) . $message, + E_USER_DEPRECATED + ); } else { - trigger_error( sprintf( '%1$s is deprecated since version %2$s with no alternative available.', $file, $version ) . $message ); + trigger_error( + sprintf( + '%1$s is deprecated since version %2$s with no alternative available.', + $file, + $version + ) . $message, + E_USER_DEPRECATED + ); } } } @@ -4624,13 +5016,14 @@ * The current behavior is to trigger a user error if WP_DEBUG is true. * * @since 3.0.0 - * @access private + * @since 5.4.0 This function is no longer marked as "private". + * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE). * * @param string $function The function that was called. * @param string $version The version of WordPress that deprecated the argument used. - * @param string $message Optional. A message regarding the change. Default null. - */ -function _deprecated_argument( $function, $version, $message = null ) { + * @param string $message Optional. A message regarding the change. Default empty. + */ +function _deprecated_argument( $function, $version, $message = '' ) { /** * Fires when a deprecated argument is called. @@ -4652,18 +5045,48 @@ */ if ( WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) { if ( function_exists( '__' ) ) { - if ( ! is_null( $message ) ) { - /* translators: 1: PHP function name, 2: version number, 3: optional message regarding the change */ - trigger_error( sprintf( __( '%1$s was called with an argument that is deprecated since version %2$s! %3$s' ), $function, $version, $message ) ); + if ( $message ) { + trigger_error( + sprintf( + /* translators: 1: PHP function name, 2: Version number, 3: Optional message regarding the change. */ + __( '%1$s was called with an argument that is deprecated since version %2$s! %3$s' ), + $function, + $version, + $message + ), + E_USER_DEPRECATED + ); } else { - /* translators: 1: PHP function name, 2: version number */ - trigger_error( sprintf( __( '%1$s was called with an argument that is deprecated since version %2$s with no alternative available.' ), $function, $version ) ); + trigger_error( + sprintf( + /* translators: 1: PHP function name, 2: Version number. */ + __( '%1$s was called with an argument that is deprecated since version %2$s with no alternative available.' ), + $function, + $version + ), + E_USER_DEPRECATED + ); } } else { - if ( ! is_null( $message ) ) { - trigger_error( sprintf( '%1$s was called with an argument that is deprecated since version %2$s! %3$s', $function, $version, $message ) ); + if ( $message ) { + trigger_error( + sprintf( + '%1$s was called with an argument that is deprecated since version %2$s! %3$s', + $function, + $version, + $message + ), + E_USER_DEPRECATED + ); } else { - trigger_error( sprintf( '%1$s was called with an argument that is deprecated since version %2$s with no alternative available.', $function, $version ) ); + trigger_error( + sprintf( + '%1$s was called with an argument that is deprecated since version %2$s with no alternative available.', + $function, + $version + ), + E_USER_DEPRECATED + ); } } } @@ -4681,14 +5104,15 @@ * functions, and so generally does not need to be called directly. * * @since 4.6.0 + * @since 5.4.0 The error type is now classified as E_USER_DEPRECATED (used to default to E_USER_NOTICE). * @access private * * @param string $hook The hook that was used. * @param string $version The version of WordPress that deprecated the hook. - * @param string $replacement Optional. The hook that should have been used. - * @param string $message Optional. A message regarding the change. - */ -function _deprecated_hook( $hook, $version, $replacement = null, $message = null ) { + * @param string $replacement Optional. The hook that should have been used. Default empty. + * @param string $message Optional. A message regarding the change. Default empty. + */ +function _deprecated_hook( $hook, $version, $replacement = '', $message = '' ) { /** * Fires when a deprecated hook is called. * @@ -4711,12 +5135,28 @@ */ if ( WP_DEBUG && apply_filters( 'deprecated_hook_trigger_error', true ) ) { $message = empty( $message ) ? '' : ' ' . $message; - if ( ! is_null( $replacement ) ) { - /* translators: 1: WordPress hook name, 2: version number, 3: alternative hook name */ - trigger_error( sprintf( __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), $hook, $version, $replacement ) . $message ); + + if ( $replacement ) { + trigger_error( + sprintf( + /* translators: 1: WordPress hook name, 2: Version number, 3: Alternative hook name. */ + __( '%1$s is deprecated since version %2$s! Use %3$s instead.' ), + $hook, + $version, + $replacement + ) . $message, + E_USER_DEPRECATED + ); } else { - /* translators: 1: WordPress hook name, 2: version number */ - trigger_error( sprintf( __( '%1$s is deprecated since version %2$s with no alternative available.' ), $hook, $version ) . $message ); + trigger_error( + sprintf( + /* translators: 1: WordPress hook name, 2: Version number. */ + __( '%1$s is deprecated since version %2$s with no alternative available.' ), + $hook, + $version + ) . $message, + E_USER_DEPRECATED + ); } } } @@ -4731,7 +5171,7 @@ * The current behavior is to trigger a user error if `WP_DEBUG` is true. * * @since 3.1.0 - * @access private + * @since 5.4.0 This function is no longer marked as "private". * * @param string $function The function that was called. * @param string $message A message explaining what has been done incorrectly. @@ -4763,30 +5203,46 @@ */ if ( WP_DEBUG && apply_filters( 'doing_it_wrong_trigger_error', true, $function, $message, $version ) ) { if ( function_exists( '__' ) ) { - if ( is_null( $version ) ) { - $version = ''; - } else { - /* translators: %s: version number */ + if ( $version ) { + /* translators: %s: Version number. */ $version = sprintf( __( '(This message was added in version %s.)' ), $version ); } - /* translators: %s: Codex URL */ + $message .= ' ' . sprintf( + /* translators: %s: Documentation URL. */ __( 'Please see Debugging in WordPress for more information.' ), - __( 'https://codex.wordpress.org/Debugging_in_WordPress' ) + __( 'https://wordpress.org/support/article/debugging-in-wordpress/' ) ); - /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: Version information message */ - trigger_error( sprintf( __( '%1$s was called incorrectly. %2$s %3$s' ), $function, $message, $version ) ); + + trigger_error( + sprintf( + /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: WordPress version number. */ + __( '%1$s was called incorrectly. %2$s %3$s' ), + $function, + $message, + $version + ), + E_USER_NOTICE + ); } else { - if ( is_null( $version ) ) { - $version = ''; - } else { + if ( $version ) { $version = sprintf( '(This message was added in version %s.)', $version ); } + $message .= sprintf( ' Please see Debugging in WordPress for more information.', - 'https://codex.wordpress.org/Debugging_in_WordPress' + 'https://wordpress.org/support/article/debugging-in-wordpress/' ); - trigger_error( sprintf( '%1$s was called incorrectly. %2$s %3$s', $function, $message, $version ) ); + + trigger_error( + sprintf( + '%1$s was called incorrectly. %2$s %3$s', + $function, + $message, + $version + ), + E_USER_NOTICE + ); } } } @@ -4801,7 +5257,8 @@ function is_lighttpd_before_150() { $server_parts = explode( '/', isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '' ); $server_parts[1] = isset( $server_parts[1] ) ? $server_parts[1] : ''; - return 'lighttpd' == $server_parts[0] && -1 == version_compare( $server_parts[1], '1.5.0' ); + + return ( 'lighttpd' === $server_parts[0] && -1 == version_compare( $server_parts[1], '1.5.0' ) ); } /** @@ -4824,7 +5281,7 @@ if ( function_exists( 'apache_get_modules' ) ) { $mods = apache_get_modules(); - if ( in_array( $mod, $mods ) ) { + if ( in_array( $mod, $mods, true ) ) { return true; } } elseif ( function_exists( 'phpinfo' ) && false === strpos( ini_get( 'disable_functions' ), 'phpinfo' ) ) { @@ -4835,6 +5292,7 @@ return true; } } + return $default; } @@ -4861,7 +5319,7 @@ * Lastly we make sure that PHP is running via FastCGI. This is important because if it runs * via ISAPI then pretty permalinks will not work. */ - $supports_permalinks = class_exists( 'DOMDocument', false ) && isset( $_SERVER['IIS_UrlRewriteModule'] ) && ( PHP_SAPI == 'cgi-fcgi' ); + $supports_permalinks = class_exists( 'DOMDocument', false ) && isset( $_SERVER['IIS_UrlRewriteModule'] ) && ( 'cgi-fcgi' === PHP_SAPI ); } /** @@ -4885,8 +5343,8 @@ * * @since 1.2.0 * - * @param string $file File path. - * @param array $allowed_files Optional. List of allowed files. + * @param string $file File path. + * @param string[] $allowed_files Optional. Array of allowed files. * @return int 0 means nothing is wrong, greater than 0 means something was wrong. */ function validate_file( $file, $allowed_files = array() ) { @@ -4906,12 +5364,12 @@ } // Files not in the allowed file list are not allowed: - if ( ! empty( $allowed_files ) && ! in_array( $file, $allowed_files ) ) { + if ( ! empty( $allowed_files ) && ! in_array( $file, $allowed_files, true ) ) { return 3; } // Absolute Windows drive paths are not allowed: - if ( ':' == substr( $file, 1, 1 ) ) { + if ( ':' === substr( $file, 1, 1 ) ) { return 2; } @@ -4923,8 +5381,6 @@ * * @since 2.6.0 * - * @staticvar bool $forced - * * @param string|bool $force Optional. Whether to force SSL in admin screens. Default null. * @return bool True if forced, false if not forced. */ @@ -4951,38 +5407,38 @@ * @return string The guessed URL. */ function wp_guess_url() { - if ( defined( 'WP_SITEURL' ) && '' != WP_SITEURL ) { + if ( defined( 'WP_SITEURL' ) && '' !== WP_SITEURL ) { $url = WP_SITEURL; } else { $abspath_fix = str_replace( '\\', '/', ABSPATH ); $script_filename_dir = dirname( $_SERVER['SCRIPT_FILENAME'] ); - // The request is for the admin + // The request is for the admin. if ( strpos( $_SERVER['REQUEST_URI'], 'wp-admin' ) !== false || strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) !== false ) { $path = preg_replace( '#/(wp-admin/.*|wp-login.php)#i', '', $_SERVER['REQUEST_URI'] ); - // The request is for a file in ABSPATH - } elseif ( $script_filename_dir . '/' == $abspath_fix ) { - // Strip off any file/query params in the path + // The request is for a file in ABSPATH. + } elseif ( $script_filename_dir . '/' === $abspath_fix ) { + // Strip off any file/query params in the path. $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['PHP_SELF'] ); } else { if ( false !== strpos( $_SERVER['SCRIPT_FILENAME'], $abspath_fix ) ) { - // Request is hitting a file inside ABSPATH + // Request is hitting a file inside ABSPATH. $directory = str_replace( ABSPATH, '', $script_filename_dir ); - // Strip off the sub directory, and any file/query params + // Strip off the subdirectory, and any file/query params. $path = preg_replace( '#/' . preg_quote( $directory, '#' ) . '/[^/]*$#i', '', $_SERVER['REQUEST_URI'] ); } elseif ( false !== strpos( $abspath_fix, $script_filename_dir ) ) { - // Request is hitting a file above ABSPATH + // Request is hitting a file above ABSPATH. $subdirectory = substr( $abspath_fix, strpos( $abspath_fix, $script_filename_dir ) + strlen( $script_filename_dir ) ); - // Strip off any file/query params from the path, appending the sub directory to the installation + // Strip off any file/query params from the path, appending the subdirectory to the installation. $path = preg_replace( '#/[^/]*$#i', '', $_SERVER['REQUEST_URI'] ) . $subdirectory; } else { $path = $_SERVER['REQUEST_URI']; } } - $schema = is_ssl() ? 'https://' : 'http://'; // set_url_scheme() is not defined yet + $schema = is_ssl() ? 'https://' : 'http://'; // set_url_scheme() is not defined yet. $url = $schema . $_SERVER['HTTP_HOST'] . $path; } @@ -5001,8 +5457,6 @@ * * @since 3.3.0 * - * @staticvar bool $_suspend - * * @param bool $suspend Optional. Suspends additions if true, re-enables them if false. * @return bool The current suspend setting */ @@ -5061,7 +5515,7 @@ $site_id = (int) $site_id; - return $site_id === get_main_site_id( $network_id ); + return get_main_site_id( $network_id ) === $site_id; } /** @@ -5105,7 +5559,7 @@ $network_id = (int) $network_id; - return ( $network_id === get_main_network_id() ); + return ( get_main_network_id() === $network_id ); } /** @@ -5152,8 +5606,6 @@ * * @since 3.0.0 * - * @staticvar bool $global_terms - * * @return bool True if multisite and global terms enabled. */ function global_terms_enabled() { @@ -5167,8 +5619,8 @@ /** * Filters whether global terms are enabled. * - * Passing a non-null value to the filter will effectively short-circuit the function, - * returning the value of the 'global_terms_enabled' site option instead. + * Returning a non-null value from the filter will effectively short-circuit the function + * and return the value of the 'global_terms_enabled' site option instead. * * @since 3.0.0 * @@ -5226,7 +5678,8 @@ * @return float|false Timezone GMT offset, false otherwise. */ function wp_timezone_override_offset() { - if ( ! $timezone_string = get_option( 'timezone_string' ) ) { + $timezone_string = get_option( 'timezone_string' ); + if ( ! $timezone_string ) { return false; } @@ -5249,9 +5702,9 @@ * @return int */ function _wp_timezone_choice_usort_callback( $a, $b ) { - // Don't use translated versions of Etc + // Don't use translated versions of Etc. if ( 'Etc' === $a['continent'] && 'Etc' === $b['continent'] ) { - // Make the order of these more like the old dropdown + // Make the order of these more like the old dropdown. if ( 'GMT+' === substr( $a['city'], 0, 4 ) && 'GMT+' === substr( $b['city'], 0, 4 ) ) { return -1 * ( strnatcasecmp( $a['city'], $b['city'] ) ); } @@ -5275,7 +5728,7 @@ } return strnatcasecmp( $a['t_city'], $b['t_city'] ); } else { - // Force Etc to the bottom of the list + // Force Etc to the bottom of the list. if ( 'Etc' === $a['continent'] ) { return 1; } @@ -5292,9 +5745,6 @@ * @since 2.9.0 * @since 4.7.0 Added the `$locale` parameter. * - * @staticvar bool $mo_loaded - * @staticvar string $locale_loaded - * * @param string $selected_zone Selected timezone. * @param string $locale Optional. Locale to load the timezones in. Default current site locale. * @return string @@ -5316,11 +5766,11 @@ $zonen = array(); foreach ( timezone_identifiers_list() as $zone ) { $zone = explode( '/', $zone ); - if ( ! in_array( $zone[0], $continents ) ) { + if ( ! in_array( $zone[0], $continents, true ) ) { continue; } - // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later + // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later. $exists = array( 0 => ( isset( $zone[0] ) && $zone[0] ), 1 => ( isset( $zone[1] ) && $zone[1] ), @@ -5350,33 +5800,33 @@ } foreach ( $zonen as $key => $zone ) { - // Build value in an array to join later + // Build value in an array to join later. $value = array( $zone['continent'] ); if ( empty( $zone['city'] ) ) { - // It's at the continent level (generally won't happen) + // It's at the continent level (generally won't happen). $display = $zone['t_continent']; } else { - // It's inside a continent group - - // Continent optgroup + // It's inside a continent group. + + // Continent optgroup. if ( ! isset( $zonen[ $key - 1 ] ) || $zonen[ $key - 1 ]['continent'] !== $zone['continent'] ) { $label = $zone['t_continent']; $structure[] = ''; } - // Add the city to the value + // Add the city to the value. $value[] = $zone['city']; $display = $zone['t_city']; if ( ! empty( $zone['subcity'] ) ) { - // Add the subcity to the value + // Add the subcity to the value. $value[] = $zone['subcity']; $display .= ' - ' . $zone['t_subcity']; } } - // Build the value + // Build the value. $value = join( '/', $value ); $selected = ''; if ( $value === $selected_zone ) { @@ -5384,13 +5834,13 @@ } $structure[] = ''; - // Close continent optgroup + // Close continent optgroup. if ( ! empty( $zone['city'] ) && ( ! isset( $zonen[ $key + 1 ] ) || ( isset( $zonen[ $key + 1 ] ) && $zonen[ $key + 1 ]['continent'] !== $zone['continent'] ) ) ) { $structure[] = ''; } } - // Do UTC + // Do UTC. $structure[] = ''; $selected = ''; if ( 'UTC' === $selected_zone ) { @@ -5399,7 +5849,7 @@ $structure[] = ''; $structure[] = ''; - // Do manual UTC offsets + // Do manual UTC offsets. $structure[] = ''; $offset_range = array( -12, @@ -5521,7 +5971,7 @@ $del_post = get_post( $post_id ); - if ( ! $del_post || 'trash' != $del_post->post_status ) { + if ( ! $del_post || 'trash' !== $del_post->post_status ) { delete_post_meta( $post_id, '_wp_trash_meta_status' ); delete_post_meta( $post_id, '_wp_trash_meta_time' ); } else { @@ -5539,7 +5989,7 @@ $del_comment = get_comment( $comment_id ); - if ( ! $del_comment || 'trash' != $del_comment->comment_approved ) { + if ( ! $del_comment || 'trash' !== $del_comment->comment_approved ) { delete_comment_meta( $comment_id, '_wp_trash_meta_time' ); delete_comment_meta( $comment_id, '_wp_trash_meta_status' ); } else { @@ -5551,11 +6001,11 @@ /** * Retrieve metadata from a file. * - * Searches for metadata in the first 8kiB of a file, such as a plugin or theme. + * Searches for metadata in the first 8 KB of a file, such as a plugin or theme. * Each piece of metadata must be on its own line. Fields can not span multiple * lines, the value will get cut at the end of the first line. * - * If the file data is not within that first 8kiB, then the author should correct + * If the file data is not within that first 8 KB, then the author should correct * their plugin file and move the data headers to the top. * * @link https://codex.wordpress.org/File_Header @@ -5563,17 +6013,17 @@ * @since 2.9.0 * * @param string $file Absolute path to the file. - * @param array $default_headers List of headers, in the format `array('HeaderKey' => 'Header Name')`. + * @param array $default_headers List of headers, in the format `array( 'HeaderKey' => 'Header Name' )`. * @param string $context Optional. If specified adds filter hook {@see 'extra_$context_headers'}. * Default empty. - * @return array Array of file headers in `HeaderKey => Header Value` format. + * @return string[] Array of file header values keyed by header name. */ function get_file_data( $file, $default_headers, $context = '' ) { // We don't need to write to the file, so just open for reading. $fp = fopen( $file, 'r' ); - // Pull only the first 8kiB of the file in. - $file_data = fread( $fp, 8192 ); + // Pull only the first 8 KB of the file in. + $file_data = fread( $fp, 8 * KB_IN_BYTES ); // PHP will close file handle, but we are good citizens. fclose( $fp ); @@ -5591,8 +6041,9 @@ * * @param array $extra_context_headers Empty array by default. */ - if ( $context && $extra_headers = apply_filters( "extra_{$context}_headers", array() ) ) { - $extra_headers = array_combine( $extra_headers, $extra_headers ); // keys equal values + $extra_headers = $context ? apply_filters( "extra_{$context}_headers", array() ) : array(); + if ( $extra_headers ) { + $extra_headers = array_combine( $extra_headers, $extra_headers ); // Keys equal values. $all_headers = array_merge( $extra_headers, (array) $default_headers ); } else { $all_headers = $default_headers; @@ -5620,7 +6071,7 @@ * * @return true True. */ -function __return_true() { +function __return_true() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return true; } @@ -5635,7 +6086,7 @@ * * @return false False. */ -function __return_false() { +function __return_false() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return false; } @@ -5648,7 +6099,7 @@ * * @return int 0. */ -function __return_zero() { +function __return_zero() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return 0; } @@ -5661,7 +6112,7 @@ * * @return array Empty array. */ -function __return_empty_array() { +function __return_empty_array() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return array(); } @@ -5674,7 +6125,7 @@ * * @return null Null value. */ -function __return_null() { +function __return_null() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return null; } @@ -5689,7 +6140,7 @@ * * @return string Empty string. */ -function __return_empty_string() { +function __return_empty_string() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore return ''; } @@ -5702,7 +6153,7 @@ * @see https://src.chromium.org/viewvc/chrome?view=rev&revision=6985 */ function send_nosniff_header() { - @header( 'X-Content-Type-Options: nosniff' ); + header( 'X-Content-Type-Options: nosniff' ); } /** @@ -5715,7 +6166,8 @@ * @return string SQL clause. */ function _wp_mysql_week( $column ) { - switch ( $start_of_week = (int) get_option( 'start_of_week' ) ) { + $start_of_week = (int) get_option( 'start_of_week' ); + switch ( $start_of_week ) { case 1: return "WEEK( $column, 1 )"; case 2: @@ -5746,7 +6198,8 @@ function wp_find_hierarchy_loop( $callback, $start, $start_parent, $callback_args = array() ) { $override = is_null( $start_parent ) ? array() : array( $start => $start_parent ); - if ( ! $arbitrary_loop_member = wp_find_hierarchy_loop_tortoise_hare( $callback, $start, $override, $callback_args ) ) { + $arbitrary_loop_member = wp_find_hierarchy_loop_tortoise_hare( $callback, $start, $override, $callback_args ); + if ( ! $arbitrary_loop_member ) { return array(); } @@ -5774,11 +6227,13 @@ * $_return_loop */ function wp_find_hierarchy_loop_tortoise_hare( $callback, $start, $override = array(), $callback_args = array(), $_return_loop = false ) { - $tortoise = $hare = $evanescent_hare = $start; - $return = array(); - - // Set evanescent_hare to one past hare - // Increment hare two steps + $tortoise = $start; + $hare = $start; + $evanescent_hare = $start; + $return = array(); + + // Set evanescent_hare to one past hare. + // Increment hare two steps. while ( $tortoise && @@ -5787,15 +6242,17 @@ ( $hare = isset( $override[ $evanescent_hare ] ) ? $override[ $evanescent_hare ] : call_user_func_array( $callback, array_merge( array( $evanescent_hare ), $callback_args ) ) ) ) { if ( $_return_loop ) { - $return[ $tortoise ] = $return[ $evanescent_hare ] = $return[ $hare ] = true; + $return[ $tortoise ] = true; + $return[ $evanescent_hare ] = true; + $return[ $hare ] = true; } - // tortoise got lapped - must be a loop + // Tortoise got lapped - must be a loop. if ( $tortoise == $evanescent_hare || $tortoise == $hare ) { return $_return_loop ? $return : $tortoise; } - // Increment tortoise by one step + // Increment tortoise by one step. $tortoise = isset( $override[ $tortoise ] ) ? $override[ $tortoise ] : call_user_func_array( $callback, array_merge( array( $tortoise ), $callback_args ) ); } @@ -5810,7 +6267,7 @@ * @see https://developer.mozilla.org/en/the_x-frame-options_response_header */ function send_frame_options_header() { - @header( 'X-Frame-Options: SAMEORIGIN' ); + header( 'X-Frame-Options: SAMEORIGIN' ); } /** @@ -5819,23 +6276,22 @@ * @since 3.3.0 * @since 4.3.0 Added 'webcal' to the protocols array. * @since 4.7.0 Added 'urn' to the protocols array. + * @since 5.3.0 Added 'sms' to the protocols array. * * @see wp_kses() * @see esc_url() * - * @staticvar array $protocols - * * @return string[] Array of allowed protocols. Defaults to an array containing 'http', 'https', * 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', - * 'mms', 'rtsp', 'svn', 'tel', 'fax', 'xmpp', 'webcal', and 'urn'. This covers - * all common link protocols, except for 'javascript' which should not be - * allowed for untrusted users. + * 'mms', 'rtsp', 'sms', 'svn', 'tel', 'fax', 'xmpp', 'webcal', and 'urn'. + * This covers all common link protocols, except for 'javascript' which should not + * be allowed for untrusted users. */ function wp_allowed_protocols() { static $protocols = array(); if ( empty( $protocols ) ) { - $protocols = array( 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn', 'tel', 'fax', 'xmpp', 'webcal', 'urn' ); + $protocols = array( 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'sms', 'svn', 'tel', 'fax', 'xmpp', 'webcal', 'urn' ); } if ( ! did_action( 'wp_loaded' ) ) { @@ -5844,7 +6300,7 @@ * * @since 3.0.0 * - * @param array $protocols Array of allowed protocols e.g. 'http', 'ftp', 'tel', and more. + * @param string[] $protocols Array of allowed protocols e.g. 'http', 'ftp', 'tel', and more. */ $protocols = array_unique( (array) apply_filters( 'kses_allowed_protocols', $protocols ) ); } @@ -5860,8 +6316,6 @@ * * @see https://core.trac.wordpress.org/ticket/19589 * - * @staticvar array $truncate_paths Array of paths to truncate. - * * @param string $ignore_class Optional. A class to ignore all function calls within - useful * when you want to just give info about the callee. Default null. * @param int $skip_frames Optional. A number of stack frames to skip - useful for unwinding @@ -5874,15 +6328,10 @@ function wp_debug_backtrace_summary( $ignore_class = null, $skip_frames = 0, $pretty = true ) { static $truncate_paths; - if ( version_compare( PHP_VERSION, '5.2.5', '>=' ) ) { - $trace = debug_backtrace( false ); - } else { - $trace = debug_backtrace(); - } - + $trace = debug_backtrace( false ); $caller = array(); $check_class = ! is_null( $ignore_class ); - $skip_frames++; // skip this function + $skip_frames++; // Skip this function. if ( ! isset( $truncate_paths ) ) { $truncate_paths = array( @@ -5896,14 +6345,14 @@ $skip_frames--; } elseif ( isset( $call['class'] ) ) { if ( $check_class && $ignore_class == $call['class'] ) { - continue; // Filter out calls + continue; // Filter out calls. } $caller[] = "{$call['class']}{$call['type']}{$call['function']}"; } else { - if ( in_array( $call['function'], array( 'do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array' ) ) ) { + if ( in_array( $call['function'], array( 'do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array' ), true ) ) { $caller[] = "{$call['function']}('{$call['args'][0]}')"; - } elseif ( in_array( $call['function'], array( 'include', 'include_once', 'require', 'require_once' ) ) ) { + } elseif ( in_array( $call['function'], array( 'include', 'include_once', 'require', 'require_once' ), true ) ) { $filename = isset( $call['args'][0] ) ? $call['args'][0] : ''; $caller[] = $call['function'] . "('" . str_replace( $truncate_paths, '', wp_normalize_path( $filename ) ) . "')"; } else { @@ -5929,15 +6378,16 @@ * @return int[] Array of IDs not present in the cache. */ function _get_non_cached_ids( $object_ids, $cache_key ) { - $clean = array(); - foreach ( $object_ids as $id ) { - $id = (int) $id; - if ( ! wp_cache_get( $id, $cache_key ) ) { - $clean[] = $id; + $non_cached_ids = array(); + $cache_values = wp_cache_get_multiple( $object_ids, $cache_key ); + + foreach ( $cache_values as $id => $value ) { + if ( ! $value ) { + $non_cached_ids[] = (int) $id; } } - return $clean; + return $non_cached_ids; } /** @@ -5976,7 +6426,7 @@ $scheme_separator = strpos( $path, '://' ); if ( false === $scheme_separator ) { - // $path isn't a stream + // $path isn't a stream. return false; } @@ -5990,12 +6440,12 @@ * * @since 3.5.0 * - * @link https://secure.php.net/manual/en/function.checkdate.php - * - * @param int $month Month number. - * @param int $day Day number. - * @param int $year Year number. - * @param string $source_date The date to filter. + * @link https://www.php.net/manual/en/function.checkdate.php + * + * @param int $month Month number. + * @param int $day Day number. + * @param int $year Year number. + * @param string $source_date The date to filter. * @return bool True if valid date, false if not valid date. */ function wp_checkdate( $month, $day, $year, $source_date ) { @@ -6032,12 +6482,12 @@ $screen = get_current_screen(); $hidden = array( 'update', 'update-network', 'update-core', 'update-core-network', 'upgrade', 'upgrade-network', 'network' ); - $show = ! in_array( $screen->id, $hidden ); + $show = ! in_array( $screen->id, $hidden, true ); /** * Filters whether to load the authentication check. * - * Passing a falsey value to the filter will effectively short-circuit + * Returning a falsey value from the filter will effectively short-circuit * loading the authentication check. * * @since 3.6.0 @@ -6116,7 +6566,7 @@ * @global int $login_grace_period * * @param array $response The Heartbeat response. - * @return array $response The Heartbeat response with 'wp-auth-check' value set. + * @return array The Heartbeat response with 'wp-auth-check' value set. */ function wp_auth_check( $response ) { $response['wp-auth-check'] = is_user_logged_in() && empty( $GLOBALS['login_grace_period'] ); @@ -6141,14 +6591,14 @@ */ function get_tag_regex( $tag ) { if ( empty( $tag ) ) { - return; + return ''; } return sprintf( '<%1$s[^<]*(?:>[\s\S]*<\/%1$s>|\s*\/>)', tag_escape( $tag ) ); } /** * Retrieve a canonical form of the provided charset appropriate for passing to PHP - * functions such as htmlspecialchars() and charset html attributes. + * functions such as htmlspecialchars() and charset HTML attributes. * * @since 3.6.0 * @access private @@ -6192,9 +6642,6 @@ * * @see reset_mbstring_encoding() * - * @staticvar array $encodings - * @staticvar bool $overloaded - * * @param bool $reset Optional. Whether to reset the encoding back to a previously-set encoding. * Default false. */ @@ -6203,7 +6650,7 @@ static $overloaded = null; if ( is_null( $overloaded ) ) { - $overloaded = function_exists( 'mb_internal_encoding' ) && ( ini_get( 'mbstring.func_overload' ) & 2 ); + $overloaded = function_exists( 'mb_internal_encoding' ) && ( ini_get( 'mbstring.func_overload' ) & 2 ); // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated } if ( false === $overloaded ) { @@ -6318,7 +6765,7 @@ * * @since 4.3.0 * - * @global WP_Post $post + * @global WP_Post $post Global post object. */ function wp_post_preview_js() { global $post; @@ -6327,7 +6774,7 @@ return; } - // Has to match the window name used in post_submit_meta_box() + // Has to match the window name used in post_submit_meta_box(). $name = 'wp-preview-' . (int) $post->ID; ?> @@ -6384,7 +6831,7 @@ return false; } - $current_limit = @ini_get( 'memory_limit' ); + $current_limit = ini_get( 'memory_limit' ); $current_limit_int = wp_convert_hr_to_bytes( $current_limit ); if ( -1 === $current_limit_int ) { @@ -6456,13 +6903,13 @@ $filtered_limit_int = wp_convert_hr_to_bytes( $filtered_limit ); if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) { - if ( false !== @ini_set( 'memory_limit', $filtered_limit ) ) { + if ( false !== ini_set( 'memory_limit', $filtered_limit ) ) { return $filtered_limit; } else { return false; } } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) { - if ( false !== @ini_set( 'memory_limit', $wp_max_limit ) ) { + if ( false !== ini_set( 'memory_limit', $wp_max_limit ) ) { return $wp_max_limit; } else { return false; @@ -6523,7 +6970,7 @@ } /** - * Get unique ID. + * Gets unique ID. * * This is a PHP implementation of Underscore's uniqueId method. A static variable * contains an integer that is incremented with each call. This number is returned @@ -6532,8 +6979,6 @@ * * @since 5.0.3 * - * @staticvar int $id_counter - * * @param string $prefix Prefix for the returned ID. * @return string Unique ID. */ @@ -6543,13 +6988,12 @@ } /** - * Get last changed date for the specified cache group. + * Gets last changed date for the specified cache group. * * @since 4.7.0 * * @param string $group Where the cache contents are grouped. - * - * @return string $last_changed UNIX timestamp with microseconds representing when the group was last changed. + * @return string UNIX timestamp with microseconds representing when the group was last changed. */ function wp_cache_get_last_changed( $group ) { $last_changed = wp_cache_get( 'last_changed', $group ); @@ -6611,12 +7055,13 @@ $email_change_email = array( 'to' => $old_email, - /* translators: Site admin email change notification email subject. %s: Site title */ + /* translators: Site admin email change notification email subject. %s: Site title. */ 'subject' => __( '[%s] Admin Email Changed' ), 'message' => $email_change_text, 'headers' => '', ); - // get site name + + // Get site name. $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); /** @@ -6663,9 +7108,9 @@ * * @since 4.9.6 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`. * - * @param string $ip_addr The IPv4 or IPv6 address to be anonymized. - * @param bool $ipv6_fallback Optional. Whether to return the original IPv6 address if the needed functions - * to anonymize it are not present. Default false, return `::` (unspecified address). + * @param string $ip_addr The IPv4 or IPv6 address to be anonymized. + * @param bool $ipv6_fallback Optional. Whether to return the original IPv6 address if the needed functions + * to anonymize it are not present. Default false, return `::` (unspecified address). * @return string The anonymized IP address. */ function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) { @@ -6733,8 +7178,8 @@ * * @since 4.9.6 * - * @param string $type The type of data to be anonymized. - * @param string $data Optional The data to be anonymized. + * @param string $type The type of data to be anonymized. + * @param string $data Optional The data to be anonymized. * @return string The anonymous data for the requested type. */ function wp_privacy_anonymize_data( $type, $data = '' ) { @@ -6753,15 +7198,16 @@ $anonymous = '0000-00-00 00:00:00'; break; case 'text': - /* translators: deleted text */ + /* translators: Deleted text. */ $anonymous = __( '[deleted]' ); break; case 'longtext': - /* translators: deleted long text */ + /* translators: Deleted long text. */ $anonymous = __( 'This content was deleted by the author.' ); break; default: $anonymous = ''; + break; } /** @@ -6854,7 +7300,7 @@ return; } - require_once( ABSPATH . 'wp-admin/includes/file.php' ); + require_once ABSPATH . 'wp-admin/includes/file.php'; $export_files = list_files( $exports_dir, 100, array( 'index.html' ) ); /** @@ -6961,7 +7407,7 @@ * * @since 5.2.0 * - * @return string $message Update PHP page annotation. An empty string if no custom URLs are provided. + * @return string Update PHP page annotation. An empty string if no custom URLs are provided. */ function wp_get_update_php_annotation() { $update_url = wp_get_update_php_url(); @@ -6972,7 +7418,7 @@ } $annotation = sprintf( - /* translators: %s: default Update PHP page URL */ + /* translators: %s: Default Update PHP page URL. */ __( 'This resource is provided by your web host, and is specific to your site. For more information, see the official WordPress documentation.' ), esc_url( $default_url ) ); @@ -7031,7 +7477,7 @@ '%2$s %3$s', esc_url( $direct_update_url ), __( 'Update PHP' ), - /* translators: accessibility text */ + /* translators: Accessibility text. */ __( '(opens in a new tab)' ) ); echo '

'; @@ -7062,7 +7508,7 @@ $dirsize = array(); } - // Exclude individual site directories from the total when checking the main site of a network + // Exclude individual site directories from the total when checking the main site of a network, // as they are subdirectories and should not be counted. if ( is_multisite() && is_main_site() ) { $dirsize[ $directory ]['size'] = recurse_dirsize( $directory, $directory . '/sites', $max_execution_time ); @@ -7084,11 +7530,11 @@ * @since 4.3.0 $exclude parameter added. * @since 5.2.0 $max_execution_time parameter added. * - * @param string $directory Full path of a directory. - * @param string|array $exclude Optional. Full path of a subdirectory to exclude from the total, or array of paths. - * Expected without trailing slash(es). - * @param int $max_execution_time Maximum time to run before giving up. In seconds. - * The timeout is global and is measured from the moment WordPress started to load. + * @param string $directory Full path of a directory. + * @param string|array $exclude Optional. Full path of a subdirectory to exclude from the total, + * or array of paths. Expected without trailing slash(es). + * @param int $max_execution_time Maximum time to run before giving up. In seconds. The timeout is global + * and is measured from the moment WordPress started to load. * @return int|false|null Size in bytes if a valid directory. False if not. Null if timeout. */ function recurse_dirsize( $directory, $exclude = null, $max_execution_time = null ) { @@ -7107,7 +7553,7 @@ return false; } - if ( $max_execution_time === null ) { + if ( null === $max_execution_time ) { // Keep the previous behavior but attempt to prevent fatal errors from timeout if possible. if ( function_exists( 'ini_get' ) ) { $max_execution_time = ini_get( 'max_execution_time' ); @@ -7122,10 +7568,11 @@ } } - if ( $handle = opendir( $directory ) ) { + $handle = opendir( $directory ); + if ( $handle ) { while ( ( $file = readdir( $handle ) ) !== false ) { $path = $directory . '/' . $file; - if ( $file != '.' && $file != '..' ) { + if ( '.' !== $file && '..' !== $file ) { if ( is_file( $path ) ) { $size += filesize( $path ); } elseif ( is_dir( $path ) ) { @@ -7148,13 +7595,13 @@ } /** -* Checks compatibility with the current WordPress version. -* -* @since 5.2.0 -* -* @param string $required Minimum required WordPress version. -* @return bool True if required version is compatible or empty, false if not. -*/ + * Checks compatibility with the current WordPress version. + * + * @since 5.2.0 + * + * @param string $required Minimum required WordPress version. + * @return bool True if required version is compatible or empty, false if not. + */ function is_wp_version_compatible( $required ) { return empty( $required ) || version_compare( get_bloginfo( 'version' ), $required, '>=' ); } @@ -7170,3 +7617,19 @@ function is_php_version_compatible( $required ) { return empty( $required ) || version_compare( phpversion(), $required, '>=' ); } + +/** + * Check if two numbers are nearly the same. + * + * This is similar to using `round()` but the precision is more fine-grained. + * + * @since 5.3.0 + * + * @param int|float $expected The expected value. + * @param int|float $actual The actual number. + * @param int|float $precision The allowed variation. + * @return bool Whether the numbers match whithin the specified precision. + */ +function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) { + return abs( (float) $expected - (float) $actual ) <= $precision; +}