: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @package WPSEO\Internals
use Yoast\WP\SEO\Integrations\Feature_Flag_Integration;
* Group of utility methods for use by WPSEO.
* All methods are static, this is just a sort of namespacing class wrapper.
* Whether the PHP filter extension is enabled.
public static $has_filters;
* Check whether file editing is allowed for the .htaccess and robots.txt files.
* {@internal current_user_can() checks internally whether a user is on wp-ms and adjusts accordingly.}}
public static function allow_system_file_edit() {
if ( current_user_can( 'edit_files' ) === false ) {
* Filter: 'wpseo_allow_system_file_edit' - Allow developers to change whether the editing of
* .htaccess and robots.txt is allowed.
* @param bool $allowed Whether file editing is allowed.
return apply_filters( 'wpseo_allow_system_file_edit', $allowed );
* Check if the web server is running on Apache or compatible (LiteSpeed).
public static function is_apache() {
if ( ! isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
$software = sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
return stripos( $software, 'apache' ) !== false || stripos( $software, 'litespeed' ) !== false;
* Check if the web server is running on Nginx.
public static function is_nginx() {
if ( ! isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
$software = sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
return stripos( $software, 'nginx' ) !== false;
* Check whether a url is relative.
* @param string $url URL string to check.
public static function is_url_relative( $url ) {
return YoastSEO()->helpers->url->is_relative( $url );
* Recursively trim whitespace round a string value or of string values within an array.
* Only trims strings to avoid typecasting a variable (to string).
* @param mixed $value Value to trim or array of values to trim.
* @return mixed Trimmed value or array of trimmed values.
public static function trim_recursive( $value ) {
if ( is_string( $value ) ) {
elseif ( is_array( $value ) ) {
$value = array_map( [ self::class, 'trim_recursive' ], $value );
* Emulate the WP native sanitize_text_field function in a %%variable%% safe way.
* Sanitize a string from user input or from the db.
* - Check for invalid UTF-8;
* - Convert single < characters to entity;
* - Remove line breaks, tabs and extra white space;
* - Strip octets - BUT DO NOT REMOVE (part of) VARIABLES WHICH WILL BE REPLACED.
* @link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/formatting.php for the original.
* @param string $value String value to sanitize.
public static function sanitize_text_field( $value ) {
$filtered = wp_check_invalid_utf8( $value );
if ( strpos( $filtered, '<' ) !== false ) {
$filtered = wp_pre_kses_less_than( $filtered );
// This will strip extra whitespace for us.
$filtered = wp_strip_all_tags( $filtered, true );
$filtered = trim( preg_replace( '`[\r\n\t ]+`', ' ', $filtered ) );
while ( preg_match( '`[^%](%[a-f0-9]{2})`i', $filtered, $match ) ) {
$filtered = str_replace( $match[1], '', $filtered );
// Strip out the whitespace that may now exist after removing the octets.
$filtered = trim( preg_replace( '` +`', ' ', $filtered ) );
* Filter a sanitized text field string.
* @param string $filtered The sanitized string.
* @param string $str The string prior to being sanitized.
return apply_filters( 'sanitize_text_field', $filtered, $value ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Using WP native filter.
* Sanitize a url for saving to the database.
* Not to be confused with the old native WP function.
* @param string $value String URL value to sanitize.
* @param array $allowed_protocols Optional set of allowed protocols.
public static function sanitize_url( $value, $allowed_protocols = [ 'http', 'https' ] ) {
$parts = wp_parse_url( $value );
if ( isset( $parts['scheme'], $parts['host'] ) ) {
$url = $parts['scheme'] . '://';
if ( isset( $parts['user'] ) ) {
$url .= rawurlencode( $parts['user'] );
$url .= isset( $parts['pass'] ) ? ':' . rawurlencode( $parts['pass'] ) : '';
$parts['host'] = preg_replace(
'`[^a-z0-9-.:\[\]\\x80-\\xff]`',
strtolower( $parts['host'] )
$url .= $parts['host'] . ( isset( $parts['port'] ) ? ':' . intval( $parts['port'] ) : '' );
if ( isset( $parts['path'] ) && strpos( $parts['path'], '/' ) === 0 ) {
$path = explode( '/', wp_strip_all_tags( $parts['path'] ) );
$path = self::sanitize_encoded_text_field( $path );
$url .= str_replace( '%40', '@', implode( '/', $path ) );
if ( isset( $parts['query'] ) ) {
wp_parse_str( $parts['query'], $parsed_query );
$parsed_query = array_combine(
self::sanitize_encoded_text_field( array_keys( $parsed_query ) ),
self::sanitize_encoded_text_field( array_values( $parsed_query ) )
$url = add_query_arg( $parsed_query, $url );
if ( isset( $parts['fragment'] ) ) {
$url .= '#' . self::sanitize_encoded_text_field( $parts['fragment'] );
if ( strpos( $url, '%' ) !== false ) {
$url = preg_replace_callback(
static function ( $octects ) {
return strtolower( $octects[0] );
return esc_url_raw( $url, $allowed_protocols );
* Decode, sanitize and encode the array of strings or the string.
* @param array|string $value The value to sanitize and encode.
* @return array|string The sanitized value.
public static function sanitize_encoded_text_field( $value ) {
if ( is_array( $value ) ) {
return array_map( [ self::class, 'sanitize_encoded_text_field' ], $value );
return rawurlencode( sanitize_text_field( rawurldecode( $value ) ) );
* Validate a value as boolean.
* @param mixed $value Value to validate.
public static function validate_bool( $value ) {
if ( ! isset( self::$has_filters ) ) {
self::$has_filters = extension_loaded( 'filter' );
if ( self::$has_filters ) {
return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
return self::emulate_filter_bool( $value );
* @param mixed $value Value to cast.
public static function emulate_filter_bool( $value ) {
if ( is_bool( $value ) ) {
elseif ( is_int( $value ) && ( $value === 0 || $value === 1 ) ) {
elseif ( ( is_float( $value ) && ! is_nan( $value ) ) && ( $value === (float) 0 || $value === (float) 1 ) ) {
elseif ( is_string( $value ) ) {
if ( in_array( $value, $true, true ) ) {
elseif ( in_array( $value, $false, true ) ) {
* Validate a value as integer.
* @param mixed $value Value to validate.
* @return int|bool Int or false in case of failure to convert to int.
public static function validate_int( $value ) {
if ( ! isset( self::$has_filters ) ) {
self::$has_filters = extension_loaded( 'filter' );
if ( self::$has_filters ) {
return filter_var( $value, FILTER_VALIDATE_INT );
return self::emulate_filter_int( $value );
* Cast a value to integer.
* @param mixed $value Value to cast.
public static function emulate_filter_int( $value ) {
if ( is_int( $value ) ) {
elseif ( is_float( $value ) ) {
// phpcs:ignore Universal.Operators.StrictComparisons -- Purposeful loose comparison.
if ( (int) $value == $value && ! is_nan( $value ) ) {
elseif ( is_string( $value ) ) {
elseif ( ctype_digit( $value ) ) {
elseif ( strpos( $value, '-' ) === 0 && ctype_digit( substr( $value, 1 ) ) ) {
* Clears the WP or W3TC cache depending on which is used.
public static function clear_cache() {
if ( function_exists( 'w3tc_pgcache_flush' ) ) {
elseif ( function_exists( 'wp_cache_clear_cache' ) ) {
public static function clear_rewrites() {
update_option( 'rewrite_rules', '' );
* Do simple reliable math calculations without the risk of wrong results.
* In the rare case that the bcmath extension would not be loaded, it will return the normal calculation results.
* @link http://floating-point-gui.de/
* @link http://php.net/language.types.float.php See the big red warning.
* @since 1.8.0 Moved from stand-alone function to this class.
* @param mixed $number1 Scalar (string/int/float/bool).
* @param string $action Calculation action to execute. Valid input:
* '+' or 'add' or 'addition',
* '-' or 'sub' or 'subtract',
* '*' or 'mul' or 'multiply',
* '/' or 'div' or 'divide',
* '%' or 'mod' or 'modulus'
* '=' or 'comp' or 'compare'.
* @param mixed $number2 Scalar (string/int/float/bool).
* @param bool $round Whether or not to round the result. Defaults to false.
* Will be disregarded for a compare operation.
* @param int $decimals Decimals for rounding operation. Defaults to 0.
* @param int $precision Calculation precision. Defaults to 10.
* @return mixed Calculation Result or false if either or the numbers isn't scalar or
* an invalid operation was passed.
* - For compare the result will always be an integer.
* - For all other operations, the result will either be an integer (preferred)
public static function calc( $number1, $action, $number2, $round = false, $decimals = 0, $precision = 10 ) {
if ( ! is_scalar( $number1 ) || ! is_scalar( $number2 ) ) {
$bc = extension_loaded( 'bcmath' );
$number1 = number_format( $number1, 10, '.', '' );
$number2 = number_format( $number2, 10, '.', '' );
$result = ( $bc ) ? bcadd( $number1, $number2, $precision ) /* string */ : ( $number1 + $number2 );
$result = ( $bc ) ? bcsub( $number1, $number2, $precision ) /* string */ : ( $number1 - $number2 );