wpvulnerability/wpvulnerability-compat.php
2026-08-23 07:08:42 +00:00

372 lines
14 KiB
PHP

<?php
/**
* WordPress core function polyfills.
*
* Contains verbatim copies of small WordPress core functions, wrapped in
* function_exists() guards so they are only defined when the running
* WordPress version does not provide them. This keeps the plugin free of
* fatals on older WordPress versions.
*
* Policy (see docs/):
* - Plain FUNCTIONS are polyfilled here, copied from WordPress core as they are.
* - Whole FUNCTIONALITIES (Application Passwords since WP 5.6, Site Health
* since WP 5.2) are NOT polyfilled; their call sites are availability-gated
* and the plugin works without them.
*
* @package WPVulnerability
*
* @since 5.1.3
*/
defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
if ( ! function_exists( 'wp_doing_cron' ) ) {
/**
* Determines whether the current request is a WordPress cron request.
*
* @since 4.8.0
*
* @return bool True if it's a WordPress cron request, false otherwise.
*/
function wp_doing_cron() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
/**
* Filters whether the current request is a WordPress cron request.
*
* @since 4.8.0
*
* @param bool $wp_doing_cron Whether the current request is a WordPress cron request.
*/
return apply_filters( 'wp_doing_cron', defined( 'DOING_CRON' ) && DOING_CRON ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved.
}
}
if ( ! function_exists( 'get_main_site_id' ) ) {
/**
* Gets the main site ID.
*
* @since 4.9.0
*
* @param int|null $network_id Optional. The ID of the network for which to get the main site.
* Defaults to the current network.
* @return int The ID of the main site.
*/
function get_main_site_id( $network_id = null ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
if ( ! is_multisite() ) {
return get_current_blog_id();
}
$network = get_network( $network_id );
if ( ! $network ) {
return 0;
}
return $network->site_id;
}
}
if ( ! function_exists( 'wp_is_json_request' ) ) {
/**
* Checks whether current request is a JSON request, or is expecting a JSON response.
*
* @since 5.0.0
*
* @return bool True if Accepts or Content-Type headers contain application/json.
* False otherwise.
*/
function wp_is_json_request() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && false !== strpos( $_SERVER['HTTP_CONTENT_TYPE'], 'application/json' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verbatim WordPress core polyfill; header used only for a strpos comparison.
return true;
}
if ( isset( $_SERVER['HTTP_ACCEPT'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT'], 'application/json' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verbatim WordPress core polyfill; header used only for a strpos comparison.
return true;
}
return false;
}
}
if ( ! function_exists( 'sanitize_locale_name' ) ) {
/**
* Strips out all characters not allowed in a locale name.
*
* @since 6.2.1
*
* @param string $locale_name The locale name to be sanitized.
* @return string The sanitized value.
*/
function sanitize_locale_name( $locale_name ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
// Limit to A-Z, a-z, 0-9, '_', '-'.
$sanitized = preg_replace( '/[^A-Za-z0-9_-]/', '', $locale_name );
/**
* Filters a sanitized locale name string.
*
* @since 6.2.1
*
* @param string $sanitized The sanitized locale name.
* @param string $locale_name The locale name before sanitization.
*/
return apply_filters( 'sanitize_locale_name', $sanitized, $locale_name ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved.
}
}
if ( ! function_exists( 'determine_locale' ) ) {
/**
* Determines the current locale desired for the request.
*
* @since 5.0.0
*
* @global string $pagenow The filename of the current screen.
*
* @return string The determined locale.
*/
function determine_locale() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
/**
* Filters the locale for the current request prior to the default determination process.
*
* Using this filter allows to override the default logic, effectively short-circuiting the function.
*
* @since 5.0.0
*
* @param string|null $locale The locale to return and short-circuit. Default null.
*/
$determined_locale = apply_filters( 'pre_determine_locale', null ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved.
if ( $determined_locale && is_string( $determined_locale ) ) {
return $determined_locale;
}
$determined_locale = get_locale();
if ( is_admin() ) {
$determined_locale = get_user_locale();
}
if ( isset( $_GET['_locale'] ) && 'user' === $_GET['_locale'] && wp_is_json_request() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verbatim WordPress core polyfill; the parameter is only compared, never stored or displayed.
$determined_locale = get_user_locale();
}
if ( ! empty( $_GET['wp_lang'] ) && isset( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verbatim WordPress core polyfill; the parameter is sanitized below and never stored or displayed.
$determined_locale = sanitize_locale_name( wp_unslash( $_GET['wp_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verbatim WordPress core polyfill; the parameter is sanitized before use and never stored or displayed.
}
/**
* Filters the locale for the current request.
*
* @since 5.0.0
*
* @param string $determined_locale The locale.
*/
return apply_filters( 'determine_locale', $determined_locale ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved.
}
}
if ( ! function_exists( 'wp_timezone_string' ) ) {
/**
* Retrieves the timezone of the site as a string.
*
* @since 5.3.0
*
* @return string PHP timezone name or a ±HH:MM offset.
*/
function wp_timezone_string() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
$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;
}
}
if ( ! function_exists( 'wp_timezone' ) ) {
/**
* Retrieves the timezone of the site as a DateTimeZone object.
*
* @since 5.3.0
*
* @return DateTimeZone Timezone object.
*/
function wp_timezone() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
return new DateTimeZone( wp_timezone_string() );
}
}
if ( ! function_exists( 'wp_date' ) ) {
/**
* Retrieves the date, in localized format.
*
* @since 5.3.0
*
* @param string $format PHP date format.
* @param int|null $timestamp Optional. Unix timestamp. Defaults to current time.
* @param DateTimeZone|null $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 ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
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( '/(?<!\\\\)r/', DATE_RFC2822, $format );
$new_format = '';
$format_length = strlen( $format );
$month = $wp_locale->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 );
// Core's wp_date() calls wp_maybe_decline_date() unconditionally, but
// that function only exists since WP 5.4. Guarded here so the polyfill
// is safe on the declared 4.7 floor (dates are then not declined).
if ( function_exists( 'wp_maybe_decline_date' ) ) {
$date = wp_maybe_decline_date( $date );
}
}
/**
* 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 ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved.
return $date;
}
}
if ( ! function_exists( 'get_post_datetime' ) ) {
/**
* Retrieves post published or modified time as a DateTimeImmutable object instance.
*
* The object will be set to the timezone from WordPress settings.
*
* @since 5.3.0
*
* @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post object.
* @param string $field Optional. Published or modified time to use from database.
* Accepts 'date' or 'modified'. Default 'date'.
* @param string $source Optional. Local or UTC time to use from database.
* Accepts 'local' or 'gmt'. Default 'local'.
* @return DateTimeImmutable|false Time object on success, false on failure.
*/
function get_post_datetime( $post = null, $field = 'date', $source = 'local' ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
$post = get_post( $post );
if ( ! $post ) {
return false;
}
$wp_timezone = wp_timezone();
if ( 'gmt' === $source ) {
$time = ( 'modified' === $field ) ? $post->post_modified_gmt : $post->post_date_gmt;
$timezone = new DateTimeZone( 'UTC' );
} else {
$time = ( 'modified' === $field ) ? $post->post_modified : $post->post_date;
$timezone = $wp_timezone;
}
if ( empty( $time ) || '0000-00-00 00:00:00' === $time ) {
return false;
}
$datetime = date_create_immutable_from_format( 'Y-m-d H:i:s', $time, $timezone );
if ( false === $datetime ) {
return false;
}
return $datetime->setTimezone( $wp_timezone );
}
}
if ( ! function_exists( 'get_post_timestamp' ) ) {
/**
* Retrieves post published or modified time as a Unix timestamp.
*
* @since 5.3.0
*
* @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post object.
* @param string $field Optional. Published or modified time to use from database.
* Accepts 'date' or 'modified'. Default 'date'.
* @return int|false Unix timestamp on success, false on failure.
*/
function get_post_timestamp( $post = null, $field = 'date' ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill.
$datetime = get_post_datetime( $post, $field );
if ( false === $datetime ) {
return false;
}
return $datetime->getTimestamp();
}
}