function esc_html( $text ) {
$safe_text = wp_check_invalid_utf8( $text );
$safe_text = _wp_specialchars( $safe_text, ENT_QUOTES );
* Filters a string cleaned and escaped for output in HTML.
* Text passed to esc_html() is stripped of invalid or special characters
* @param string $safe_text The text after it has been escaped.
* @param string $text The text prior to being escaped.
return apply_filters( 'esc_html', $safe_text, $text );
* Escaping for HTML attributes.
function esc_attr( $text ) {
$safe_text = wp_check_invalid_utf8( $text );
$safe_text = _wp_specialchars( $safe_text, ENT_QUOTES );
* Filters a string cleaned and escaped for output in an HTML attribute.
* Text passed to esc_attr() is stripped of invalid or special characters
* @param string $safe_text The text after it has been escaped.
* @param string $text The text prior to being escaped.
return apply_filters( 'attribute_escape', $safe_text, $text );
* Escaping for textarea values.
function esc_textarea( $text ) {
$safe_text = htmlspecialchars( $text, ENT_QUOTES, get_option( 'blog_charset' ) );
* Filters a string cleaned and escaped for output in a textarea element.
* @param string $safe_text The text after it has been escaped.
* @param string $text The text prior to being escaped.
return apply_filters( 'esc_textarea', $safe_text, $text );
* Escaping for XML blocks.
* @param string $text Text to escape.
* @return string Escaped text.
function esc_xml( $text ) {
$safe_text = wp_check_invalid_utf8( $text );
$cdata_regex = '\<\!\[CDATA\[.*?\]\]\>';
(?=.*?{$cdata_regex}) # lookahead that will match anything followed by a CDATA Section
(?<non_cdata_followed_by_cdata>(.*?)) # the "anything" matched by the lookahead
(?<cdata>({$cdata_regex})) # the CDATA Section matched by the lookahead
(?<non_cdata>(.*)) # non-CDATA Section
$safe_text = (string) preg_replace_callback(
static function( $matches ) {
if ( ! empty( $matches['non_cdata'] ) ) {
// escape HTML entities in the non-CDATA Section.
return _wp_specialchars( $matches['non_cdata'], ENT_XML1 );
// Return the CDATA Section unchanged, escape HTML entities in the rest.
return _wp_specialchars( $matches['non_cdata_followed_by_cdata'], ENT_XML1 ) . $matches['cdata'];
* Filters a string cleaned and escaped for output in XML.
* Text passed to esc_xml() is stripped of invalid or special characters
* before output. HTML named character references are converted to their
* equivalent code points.
* @param string $safe_text The text after it has been escaped.
* @param string $text The text prior to being escaped.
return apply_filters( 'esc_xml', $safe_text, $text );
* Escape an HTML tag name.
* @param string $tag_name
function tag_escape( $tag_name ) {
$safe_tag = strtolower( preg_replace( '/[^a-zA-Z0-9_:]/', '', $tag_name ) );
* Filters a string cleaned and escaped for output as an HTML tag.
* @param string $safe_tag The tag name after it has been escaped.
* @param string $tag_name The text before it was escaped.
return apply_filters( 'tag_escape', $safe_tag, $tag_name );
* Convert full URL paths to absolute paths.
* Removes the http or https protocols and the domain. Keeps the path '/' at the
* beginning, so it isn't a true relative link, but from the web root base.
* @since 4.1.0 Support was added for relative URLs.
* @param string $link Full URL path.
* @return string Absolute path.
function wp_make_link_relative( $link ) {
return preg_replace( '|^(https?:)?//[^/]+(/?.*)|i', '$2', $link );
* Sanitises various option values based on the nature of the option.
* This is basically a switch statement which will pass $value through a number
* of functions depending on the $option.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $option The name of the option.
* @param string $value The unsanitised value.
* @return string Sanitized value.
function sanitize_option( $option, $value ) {
$original_value = $value;
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
$value = sanitize_email( $value );
if ( ! is_email( $value ) ) {
$error = __( 'The email address entered did not appear to be a valid email address. Please enter a valid email address.' );
case 'medium_large_size_w':
case 'medium_large_size_h':
case 'comment_max_links':
case 'rss_excerpt_length':
case 'default_email_category':
case 'default_link_category':
case 'close_comments_days_old':
case 'comments_per_page':
case 'thread_comments_depth':
case 'users_can_register':
$value = absint( $value );
case 'default_ping_status':
case 'default_comment_status':
// Options that if not there have 0 value but need to be something like "closed".
if ( '0' == $value || '' === $value ) {
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( $value !== $original_value ) {
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', wp_encode_emoji( $original_value ) );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
$value = esc_html( $value );
$value = preg_replace( '/[^a-zA-Z0-9_-]/', '', $value ); // Strips slashes.
// This is the value if the settings checkbox is not checked on POST. Don't rely on this.
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
$value = strip_tags( $value );
$value = wp_kses_data( $value );
$value = explode( "\n", $value );
$value = array_filter( array_map( 'trim', $value ) );
$value = array_filter( array_map( 'esc_url_raw', $value ) );
$value = implode( "\n", $value );
$value = preg_replace( '/[^0-9:.-]/', '', $value ); // Strips slashes.
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
if ( preg_match( '#http(s?)://(.+)#i', $value ) ) {
$value = esc_url_raw( $value );
$error = __( 'The WordPress address you entered did not appear to be a valid URL. Please enter a valid URL.' );
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
if ( preg_match( '#http(s?)://(.+)#i', $value ) ) {
$value = esc_url_raw( $value );
$error = __( 'The Site address you entered did not appear to be a valid URL. Please enter a valid URL.' );
$allowed = get_available_languages();
if ( ! is_multisite() && defined( 'WPLANG' ) && '' !== WPLANG && 'en_US' !== WPLANG ) {
if ( ! in_array( $value, $allowed, true ) && ! empty( $value ) ) {
$value = get_option( $option );
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
if ( ! is_array( $value ) ) {
$value = explode( ' ', $value );
$value = array_values( array_filter( array_map( 'trim', $value ) ) );
case 'limited_email_domains':
case 'banned_email_domains':
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
if ( ! is_array( $value ) ) {
$value = explode( "\n", $value );
$domains = array_values( array_filter( array_map( 'trim', $value ) ) );
foreach ( $domains as $domain ) {
if ( ! preg_match( '/(--|\.\.)/', $domain ) && preg_match( '|^([a-zA-Z0-9-\.])+$|', $domain ) ) {
$allowed_zones = timezone_identifiers_list();
if ( ! in_array( $value, $allowed_zones, true ) && ! empty( $value ) ) {
$error = __( 'The timezone you have entered is not valid. Please select a valid timezone.' );
case 'permalink_structure':
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
$value = esc_url_raw( $value );
$value = str_replace( 'http://', '', $value );
if ( 'permalink_structure' === $option && '' !== $value && ! preg_match( '/%[^\/%]+%/', $value ) ) {
/* translators: %s: Documentation URL. */
__( 'A structure tag is required when using custom permalinks. <a href="%s">Learn more</a>' ),
__( 'https://wordpress.org/support/article/using-permalinks/#choosing-your-permalink-structure' )
if ( ! get_role( $value ) && get_role( 'subscriber' ) ) {
$value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
if ( is_wp_error( $value ) ) {
$error = $value->get_error_message();
$value = explode( "\n", $value );
$value = array_filter( array_map( 'trim', $value ) );
$value = array_unique( $value );
$value = implode( "\n", $value );
if ( ! empty( $error ) ) {
$value = get_option( $option );
if ( function_exists( 'add_settings_error' ) ) {
add_settings_error( $option, "invalid_{$option}", $error );
* Filters an option value following sanitization.
* @since 4.3.0 Added the `$original_value` parameter.
* @param string $value The sanitized option value.
* @param string $option The option name.
* @param string $original_value The original value passed to the function.
return apply_filters( "sanitize_option_{$option}", $value, $option, $original_value );
* Maps a function to all non-iterable elements of an array or an object.
* This is similar to `array_walk_recursive()` but acts upon objects too.
* @param mixed $value The array, object, or scalar.
* @param callable $callback The function to map onto $value.
* @return mixed The value with the callback applied to all non-arrays and non-objects inside it.
function map_deep( $value, $callback ) {
if ( is_array( $value ) ) {
foreach ( $value as $index => $item ) {
$value[ $index ] = map_deep( $item, $callback );
} elseif ( is_object( $value ) ) {
$object_vars = get_object_vars( $value );
foreach ( $object_vars as $property_name => $property_value ) {
$value->$property_name = map_deep( $property_value, $callback );
$value = call_user_func( $callback, $value );
* Parses a string into variables to be stored in an array.
* @param string $string The string to be parsed.
* @param array $array Variables will be stored in this array.
function wp_parse_str( $string, &$array ) {
parse_str( $string, $array );
* Filters the array of variables derived from a parsed string.
* @param array $array The array populated with variables.
$array = apply_filters( 'wp_parse_str', $array );
* Convert lone less than signs.
* KSES already converts lone greater than signs.
* @param string $text Text to be converted.
* @return string Converted text.
function wp_pre_kses_less_than( $text ) {
return preg_replace_callback( '%<[^>]*?((?=<)|>|$)%', 'wp_pre_kses_less_than_callback', $text );