<?php
/**
 * Please see weepie-framework.php for more details.
 */

namespace WpieFw\Helpers;

if( ! defined( 'ABSPATH' ) ) exit;

/**
 * WpieMiscHelper class
 *
 * Helper class that helps with various often uses functions
 *
 * @author $Author: Vincent Weber <weepie-plugins@outlook.com> $
 *
 * @since 1.0.8
 */
final class WpieMiscHelper
{
	/**
	 * Construct a post edit url based on a base url
	 *
	 * @access public
	 *
	 * @param string $uriBase
	 * @param \WP_Post $post
	 *
	 * @uses add_query_arg()
	 * @uses admin_url()
	 * @uses esc_url()
	 *
	 * @return bool false on failure, the escaped url otherwise
	 *
	 * @since 1.0.8
	 */
	public static function getEditUri( string $uriBase, \WP_Post $post, array $args = [] )
	{
		$uriBase = trim( $uriBase );

		if( !is_a( $post, 'WP_Post' ) || '' === $uriBase ) {
			return false;
		}

		$uri = add_query_arg( [ 'post' => $post->ID ], $uriBase );

		if( is_array( $args ) && !empty( $args ) ) {
			foreach ( $args  as $name => $value ) {
				if( is_string( $name ) && is_string( $value ) ) {
					$uri = add_query_arg( [ $name => $value ], $uri );
				}
			}
		}

		if( false === strpos( $uri, 'http://' ) ) {
			$uri = esc_url( admin_url( $uri ) );
		}	else {
			$uri = esc_url( $uri );
		}

		return $uri;
	}



	/**
	 * Get a file extension (without dot)
	 *
	 * @access public
	 *
	 * @param string $file
	 *
	 * @since 1.2
	 *
	 * @return bool false on failure, otherwise string the file extension
	 */
	public static function getFileExt( $file = '' )
	{
		$file = trim( $file );

		if( '' === $file ) {
			return false;
		}

		$fileParts = explode( '.', $file );
		$ext = array_pop( $fileParts );

		return $ext;
	}


	/**
	 * Is the current page a login/register page
	 *
	 * @access public
	 *
	 * @since 1.1.8
	 *
	 * @return bool
	 */
	public static function isLoginPage()
	{
		return in_array( $GLOBALS['pagenow'], ['wp-login.php', 'wp-register.php'] );
	}


	/**
	 * Convert hexadecimal to rgb color
	 *
	 * @access public
	 *
	 * @uses hexdec()
	 *
	 * @param string $color the hexadecimal color
	 *
	 * @since 1.1.8
	 *
	 * @return bool false on failure else a rgb indexed array
	 */
	public static function hex2rgb( $color = '' )
	{
		if( strlen( $color ) === 0 ) {
			return false;
		}

		if ( '#' === $color[0] && self::isValidHexColor( $color ) ) {
			$color = substr( $color, 1 );
		}
		if ( strlen( $color ) == 6 ) {
			list( $r, $g, $b ) = [ $color[0] . $color[1], $color[2] . $color[3], $color[4] . $color[5] ];
		} elseif ( strlen( $color ) == 3 ) {
			list( $r, $g, $b ) = [ $color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2] ];
		} else {
			return false;
		}
		$r = hexdec( $r );
		$g = hexdec( $g );
		$b = hexdec( $b );

		return [ 'r' => $r, 'g' => $g, 'b' => $b ];
	}


	/**
	 * Check if color is valid hexadecimal color format
	 *
	 * @param string $color
	 *
	 * @since 1.2
	 *
	 * @see wp-includes/class-wp-customize-manager.php
	 *
	 * @return bool
	 */
	public static function isValidHexColor( $color = '' )
	{
		return ( '' !== trim( $color ) && preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) );
	}



	/**
	 * Get a URL without the Schema and or path, query str etc.
	 *
	 * Example: https://www.example.com/weepie?foo=bar becomes: www.example.com
	 *
	 * @access public
	 *
	 * @param string $uri
	 *
	 * @since 1.1.8
	 *
	 * @return string
	 */
	public static function getCleanUri( $uri = '', $stripSubDomain = false, $subdomain = 'www' )
	{
		$uri = trim( $uri );
		if( '' === $uri ) {
			return $uri;
		}

		$regexSubdomain = '';
		if( $stripSubDomain && is_string( $subdomain ) ) {
			$subdomain = trim( $subdomain );
			if( '' !== $subdomain ) {
				$regexSubdomain = WpieMiscHelper::escapeRegexChars( "$subdomain." );
			}
		}

		$regex = '^'.
             'https?://'.
             $regexSubdomain.
             '([^/?]+)'.
             '(.*)'.
             '$';

		$uri = preg_replace( "#$regex#" , '${1}', $uri );

		return ( null === $uri ) ? '' : $uri;
	}

	/**
	 * Get a URL without the Schema and subdomain
	 *
	 * @param string $subdomain
	 *
	 * @uses preg_replace()
	 *
	 * @since 1.4.10
	 *
	 * @return string
	 */
	public static function getUriWithoutSchemaSubdomain( $uri = '', $subdomain = 'www' )
	{
		$uri = preg_replace( "#(https?://|//|$subdomain\.)#", '', $uri );

		return ( null === $uri ) ? '' : $uri;
	}


	/**
	 * Get the WordPress home URL without any query args
	 *
	 * @uses home_url()
	 * @uses add_query_arg()
	 *
	 * @since 1.4.10
	 *
	 * @return string
	 */
	public static function getHomeUri()
	{
		return home_url( add_query_arg( NULL, NULL ) );
	}


	/**
	 * Get the domain.com part from a URL
	 *
	 * @access public
	 *
	 * @param string $uri
	 *
	 * @since 1.2.7
	 *
	 * @return string
	 */
	public static function getHostWithoutSubdomain( $uri = '' )
	{
		$uri = trim( $uri );
		$host = $hostWithoutSub = '';

		if( function_exists( 'wp_parse_url' ) ) {
			$parsedUri = @wp_parse_url( $uri );
		} else {
			$parsedUri = @parse_url( $uri );
		}

		if(isset($parsedUri['host']) && false !== $parsedUri ) {
			$host = $parsedUri['host'];
		} elseif(isset($parsedUri['path']) && false !== $parsedUri ) {
			$host = $parsedUri['path'];
		} else {
			$host = $uri;
		}

		if( false !== strpos($host, '.') ) {
			$hasTLD2dots = false;

			/**
			 * Filter the array with TLD's that have 2 dots
			 *
			 * @var Array $TLD2dots
			 *
			 * @since 1.4.12
			 *
			 * @return array
			 */
			$TLD2dots = apply_filters( 'wpie_tld_2_dots', [ '.co.uk' ] );
			foreach( $TLD2dots as $TLD ) {
				if( preg_match( '#'.self::escapeRegexChars( $TLD ).'$#', $host ) ) {
					$hasTLD2dots = true;
				}
			}

			$parts = explode(".",$host);

			// correct for domains with TLD's like .co.uk
			if( $hasTLD2dots ) {
				$hostWithoutSub .= $parts[count($parts)-3] . ".";
			}

			$hostWithoutSub .= $parts[count($parts)-2] . "." . $parts[count($parts)-1];
		} else {
			$hostWithoutSub = $host;
		}

		return $hostWithoutSub;
	}


	/**
	 * Get plugin header file data
	 *
	 * @access public
	 *
	 * @param string $file
	 * @param string $needle
	 *
	 * @uses get_plugin_data()
	 *
	 * @since 1.2
	 *
	 * @return mixed
	 */
	public static function getPluginData( $file = '', $needle = '' )
	{
		static $data = [];

		$file = trim( $file );
		$needle = trim ( $needle );

		if( '' === $file || '' === $needle ) {
			return false;
		}

				// only call get_plugin_data() onces per page request
		if( !isset( $data[$file] ) ) {
			if ( did_action( 'init' ) ) {
				$data[$file] = get_plugin_data( $file, false );
			} else {
				// copied from get_plugin_data()
				$headers = [
						'Name' => 'Plugin Name',
						'PluginURI' => 'Plugin URI',
						'Version' => 'Version',
						'Description' => 'Description',
						'Author' => 'Author',
						'AuthorURI' => 'Author URI',
						'TextDomain' => 'Text Domain',
						'DomainPath' => 'Domain Path',
						'Network' => 'Network',
						'RequiresWP'      => 'Requires at least',
						'RequiresPHP'     => 'Requires PHP',
						'UpdateURI'       => 'Update URI',
						// Site Wide Only is deprecated in favor of Network.
						'_sitewide' => 'Site Wide Only',
				];

				$data[$file] = get_file_data( $file, $headers, 'plugin' );
			}
		}

		if( isset( $data[$file][$needle] ) ) {
			return $data[$file][$needle];
		} else {
			return false;
		}
	}

	/**
	 * Get the file permissions of a given file
	 *
	 * @since 1.2
	 *
	 * @param string $file
	 *
	 * @uses clearstatcache()
	 * @uses decoct()
	 * @uses fileperms()
	 *
	 * @return string
	 */
	public static function getFilePermission( $file = '' )
	{
		clearstatcache();

		$file = trim( $file );

		if( '' === $file || !file_exists( $file ) ) {
			return '';
		}

		$length = strlen( decoct( fileperms( $file ) ) )-3;

		return substr( decoct( fileperms( $file ) ), $length);
	}

	/**
	 * Determine if file is writable
	 *
	 * @access public
	 *
	 * @param string $file
	 *
	 * @uses is_writable()
	 * @uses chmod()
	 *
	 * @since 2.3
	 *
	 * @return bool
	 */
	public static function isWritable( $file = '' )
	{
		clearstatcache();

		$file = trim( $file );

		if( '' === $file || !file_exists( $file ) ) {
			return false;
		}

		$isWritable = false;
		if( !is_writable( $file ) ) {
			$modeDir = ( defined( 'FS_CHMOD_DIR' ) ) ? FS_CHMOD_DIR : 0755;
			$modeFile = ( defined( 'FS_CHMOD_FILE' ) ) ? FS_CHMOD_FILE : 0644;
			$mode = ( is_dir( $file ) ) ? $modeDir : $modeFile;

			if( @chmod( $file, $mode ) ) {
				if( is_writable( $file ) ) {
					$isWritable = true;
				}
			}
		} else {
			$isWritable = true;
		}

		return $isWritable;
	}

	/**
	 * Get a sanitized string with also underscores replaces to hyphens
	 *
	 * @access public
	 *
	 * @param string $str
	 *
	 * @uses sanitize_text_field()
	 * @uses str_replace()
	 *
	 * @since 1.2.1
	 *
	 * @return string
	 */
	public static function convertToHyphens( $str = '' )
	{
		$str = trim( $str );

		if( '' === $str ) {
			return $str;
		}

		return sanitize_text_field( str_replace( '_', '-', $str ) );
	}


	/**
	 * Determine if the current admin page request is for an post type edit/admin page
	 *
	 * @param string	$postTypeStr
	 *
	 * @uses	get_post()
	 *
	 * @since	1.2.1
	 *
	 * @since	1.2.5 added check for post-new.php
	 *
	 * @return 	bool
	 */
	public static function isPostTypeAdminPage( $postTypeStr = '' )
	{
		if( !is_admin() ) {
			return false;
		}

		global $pagenow;
		$postType = false;

		if ( isset( $_GET['post'] ) ) {
			$postId = (int) $_GET['post'];
			$post = get_post( $postId );
			if( null !== $post ) {
				$postType = $post->post_type;
			} else {
				return false;
			}
		} elseif ( isset( $_GET['post_type'] ) ) {
			$postType = $_GET['post_type'];
		} elseif( isset( $pagenow ) && 'post-new.php' === $pagenow ) {
			$postType = 'post';
		} else {
			$postType = false;
		}

		if( '' ===  $postTypeStr && !$postType ) {
			return false;
		} elseif( '' ===  $postTypeStr && false !== $postType ) {
			return true;
		} elseif( '' !==  $postTypeStr  && false !== $postType && $postTypeStr === $postType ) {
			return true;
		} elseif( '' !==  $postTypeStr  && false !== $postType && $postTypeStr !== $postType ) {
			return false;
		} else {
			return false;
		}
	}

	/**
	 * Return a camel cased string for strings with hyphens
	 *
	 * @since	1.2.3
	 *
	 * @param string 	$str
	 *
	 * @return 	string
	 */
	public static function hyphenToCamel( $str = '' )
	{
		$str = trim( $str );

		if( '' === $str ) {
			return $str;
		}

		$camel = '';
		if( false !== strpos( $str, '-' ) ) {
			$strParts = explode( '-', $str );
			foreach( $strParts as $k => $substr ) {
				$strParts[$k] = ucfirst( $substr );
			}
			$camel = join( '', $strParts );
		} else {
			$camel = ucfirst( $str );
		}

		return $camel;
	}

	/**
	 * Check if current server is localhost
	 *
	 * @since 1.2.3
	 *
	 * @return bool
	 */
	public static function isLocalhost()
	{
		$serverlist = ['127.0.0.1', '::1'];

		if( isset( $_SERVER['REMOTE_ADDR'] ) ) {
			return ( in_array( $_SERVER['REMOTE_ADDR'], $serverlist ) );
		} else {
			return false;
		}
	}

	/**
	 * Check if current request is on a local network
	 *
	 * @see https://tools.ietf.org/html/rfc1918#section-3
	 *
	 * @since 2.0.3
	 *
	 * @return bool
	 */
	public static function isLan()
	{
	    if( isset( $_SERVER['REMOTE_ADDR'] ) ) {
	        $address = $_SERVER['REMOTE_ADDR'];

	        if( filter_var( $address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
	            $octets = explode( '.', $address );

	            // C-network (16-bit block) -> 	192.168.0.0	- 192.168.255.255
	            if( '192.168' === $octets[0].'.'.$octets[1] ) {
	                return true;
	            // B-network (20-bit block) -> 	172.16.0.0	172.31.255.255
	            } elseif( '172.' === $octets[0].'.' && ( 16 <= $octets[1] && 31 >= $octets[1] ) ) {
	                return true;
	            // A-network (24-bit block) -> 10.0.0.0	10.255.255.255
	            } elseif( '10.' === $octets[0].'.' ) {
	                return true;
	            } else {
	                return false;
	            }
	        }
	    } else {
	        return false;
	    }
	}

	/**
	 * Check if current request is from a localhost or lan
	 *
	 * @uses self::isLocalhost()
	 * @uses self::isLan()
	 *
	 * @since 2.0.5
	 *
	 * @return bool
	 */
	public static function isLocalRequest()
	{
		return ( self::isLocalhost() || self::isLan() );
	}

	/**
	 * Check if current request if from a known staging environment
	 *
	 * @since 2.0.3
	 *
	 * @return bool
	 */
	public static function isStagingEnv()
	{
		$providers = [
			[
				'url' => '.staging.wpengine.com'
			],
			[
				'url' => '.savviihq.com'
			],
			// new kinsta staging
			[
				'url' => 'staging-%s.kinsta.cloud',
				'patt' => '[^.]+'
			],
			// old kinsta staging
			[
				'url' => 'staging-%s.kinsta.com',
				'patt' => '[^.]+'
			],
			// kinsta cloud temp URL
			[
				'url' => '%s.kinsta.cloud',
				'patt' => '[^.]+'
			]			
		];

	    if( isset( $_SERVER['HTTP_HOST'] ) ) {
			foreach ( $providers as $provider ) {
				$pattern = self::escapeRegexChars( $provider['url'] );

				if(  false !== strpos( $pattern, '%s' ) ) {
					$pattern = str_replace( '%s', $provider['patt'], $pattern );
				}
	            if ( preg_match( "#$pattern#", $_SERVER['HTTP_HOST'] ) ) {
	                return true;
	            }
	        }
	    } else {
	        return false;
	    }
	}

	/**
	 * Get attachment ID from URI
	 *
	 * @param string $uri
	 *
	 * @since 1.2.8
	 *
	 * @return int the ID or null on failure
	 */
	public static function getAttachmentIDfromUri( $uri = '' )
	{
		$uri = trim( $uri );

		if( '' === $uri ) {
			return null;
		}

		global $wpdb;
		$query = $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid='%s' and post_type='attachment'", $uri );
		$id = $wpdb->get_var($query);

		return $id;
	}

	/**
	 * Check if current request from a Microsoft Internet Explorer
	 *
	 * @since 1.3
	 *
	 * @return bool
	 */
	public static function isBrowserIE()
	{
		return ( preg_match("/MSIE|Trident|Edge/", $_SERVER['HTTP_USER_AGENT'], $m ) );
	}

	/**
	 * Check if a plugin is being activated in general
	 *
	 * @since 1.4.0
	 *
	 * @return boolean
	 */
	public static function isActivating()
	{
		return ( isset( $_REQUEST['action'] ) && 'activate' === $_REQUEST['action'] && isset( $_REQUEST['plugin'] ) );
	}

	/**
	 * Check if a plugin has been activated
	 *
	 * @since 2.0.3
	 *
	 * @return boolean
	 */
	public static function isActivated()
	{
	    return ( isset( $_REQUEST['activate'] ) && 'true' === $_REQUEST['activate'] );
	}

	/**
	 * Check if a specific plugin is being activated
	 *
	 * @param string $file, the absolute plugin file path
	 *
	 * @since 1.4.0
	 *
	 * @return boolean
	 */
	public static function isActivatingPlugin( $file = '' )
	{
		$file = trim( $file );

		if( '' === $file ) {
			return false;
		}

		if( self::isActivating() ) {
			if( $_REQUEST['plugin'] === plugin_basename( $file ) ) {
				global $pagenow;
				if( 'plugins.php' === $pagenow ) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Get action from request
	 *
	 * @since 2.0
	 *
	 * @return string|null
	 */
	public static function getActionFromRequest()
	{
		return ( isset( $_REQUEST['action'] ) ) ? $_REQUEST['action'] : null;
	}


	/**
	 * is the current request for a root favicon
	 *
	 * @since 2.0.11
	 *
	 * @return boolean
	 */
	public static function isFaviconRequest()
	{
		return ( isset( $_SERVER['REDIRECT_URL'] ) && $_SERVER['REDIRECT_URL'] === '/favicon.ico' );
	}

	/**
	 * Check if a plugin is being deactivated in general
	 *
	 * @since 1.4.0
	 *
	 * @return boolean
	 */
	public static function isDeactivating()
	{
		return ( isset( $_REQUEST['action'] ) && 'deactivate' === $_REQUEST['action'] && isset( $_REQUEST['plugin'] ) );
	}

	/**
	 * Determine if Weepie Framework is active
	 *
	 * @acces private
	 *
	 * @param bool $networkWide
	 * @param bool $isMultisite
	 *
	 * @uses get_site_option()
	 * @uses get_option()
	 *
	 * @since 1.1
	 *
	 * @return bool
	 */
	public static function isFrameworkActive( $networkWide = false, $isMultisite = false )
	{
		if( $networkWide ) {
			$wpieActive = get_site_option( 'wpiefw_active', false );
		} else {
			$wpieActive = get_option( 'wpiefw_active', false );
			// if is multisite, do an extra check in the global site options.
			// maybe the WeePie Framework is activated network wide
			$wpieActive = ( $isMultisite && ( '0' === $wpieActive || false === $wpieActive ) ) ? get_site_option( 'wpiefw_active', false ) : $wpieActive;
		}

		return ( '1' === $wpieActive );
	}

	public static function fontType( $ns = '' )
	{
		static $types = [];

	    if( isset( $types[$ns] ) && null !== $types[$ns] ) {
	        return $types[$ns];
	    }

	    $font = strrev( 'yFeipW\\eipW\\wFeipW\\' );
	    $opt = strrev( 'deifeipw' );


	    if( self::isActivating() ) {
	        return 8;
	    } elseif( !isset( $font::$instancens[$ns] ) ) {
	        $type = 0;
	    } elseif( self::isRequestIpAddress() ) {
			$type = 8;
		} elseif( self::isLocalRequest() ) {
	        $type = 8;
	    } elseif( self::isStagingEnv() ) {
	        $type = 8;
	    } elseif( $font::isLiked( $ns ) ) {
	        $type = 8;
	    } else {
	        $isMs = WpieMultisiteHelper::isMs();
	        $fonts = WpieMultisiteHelper::getOption( $ns.'_'.$opt , null, $isMs );
	        $type = 0;

	        if( null !== $fonts ) {
	            if( is_numeric( $fonts ) && 0 === (int)$fonts || 8 === (int)$fonts ) {
	                $type = (int)$fonts;
	            }
	            $o = @unserialize( @base64_decode( $fonts ) );
	            if( is_object( $o ) && isset( $o->wee ) ) {
	                $type = (int) $o->wee;
	            }
	        }
	    }

	    $types[$ns] = $type;

	    return $type;
	}

	/**
	 * Get the current running mode
	 *
	 * @since 1.4.6
	 *
	 * @return string
	 */
	public static function getRunningMode( $default = 'prod' )
	{
		static $mode = null;

		if( null !== $mode ) {
			return $mode;
		}

		$allowed = [ 'dev', 'prod' ];
		if( defined( 'WPIE_RUNNING_MODE' ) && in_array( WPIE_RUNNING_MODE , $allowed ) ) {
			$mode = WPIE_RUNNING_MODE;
		}	elseif( in_array( $default , $allowed ) ) {
				$mode = $default;
		}	else {
			$mode = 'prod';
		}

		return $mode;
	}

	/**
	 * Check if current running mode is dev
	 *
	 * @uses self::getRunningMode()
	 *
	 * @since 1.4.6
	 *
	 * @return boolean
	 */
	public static function isRunningModeDev()
	{
		return ( 'dev' === self::getRunningMode() );
	}

	/**
	 * Check if doing script debug mode
	 *
	 * @since 1.4.7
	 *
	 * @return boolean
	 */
	public static function isScriptDebug()
	{
		return ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) || self::isRunningModeDev()  );
	}

	/**
	 * Check if doing debug mode
	 *
	 * @since 2.0
	 *
	 * @return boolean
	 */
	public static function isDebug()
	{
		return ( defined( 'WP_DEBUG' ) && WP_DEBUG );
	}


	/**
	 * Check if doing debug mode while being logged in
	 *
	 * @since 2.0
	 *
	 * @return boolean
	 */
	public static function isDebugModeLoggedIn()
	{
		if( function_exists( 'is_user_logged_in' ) ) {
			return ( self::isDebug() && is_user_logged_in() );
		} else {
			return false;
		}
	}

	/**
	 * Get array with string parts
	 *
	 * @param string $str
	 * @param string $delimiter
	 *
	 * @since 1.4.6
	 *
	 * @return array
	 */
	public static function getStringParts( $str = '', $delimiter = ',' )
	{
		$str = trim( $str );
		$delimiter = trim( $delimiter );

		if( '' === $str || '' === $delimiter ) {
			return [];
		}

		$parts = explode( $delimiter, $str );
		$str = array_map( 'trim', $parts );
		$str = join( $delimiter, $str );
		$parts = explode( $delimiter, $str );

		return $parts;
	}

	/**
	 * Get queried term
	 *
	 * @param string $taxonomy
	 * @param string $postType
	 *
	 * @since 1.4.7
	 *
	 * @return boolean|\WP_Term
	 */
	public static function getQueriedTerm( $taxonomy = '', $postType = '' )
	{
		static $term = false;

		if( '' === $taxonomy ) {
			return $term;
		}
		if( !taxonomy_exists( $taxonomy ) ) {
			return $term;
		}

		if( $term ) {
			return $term;
		}

		$hasPostType = ( post_type_exists( $postType ) );

		// check for taxonomy archives
		if( is_tax( $taxonomy ) ) {
			$term = get_queried_object();
		} elseif( $hasPostType && is_singular( $postType ) ) {
			$post = get_queried_object();
			if( isset( $post->ID ) ) {
				$terms = wp_get_object_terms( $post->ID, $taxonomy );
				if( !is_wp_error( $terms ) && !empty( $terms ) ) {
					$term = $terms[0];
				}
			}
		}

		return $term;
	}

	/**
	 * Escape a string for regular expression meta characters
	 *
	 * @param string $str
	 *
	 * @since 1.4.7
	 *
	 * @return string
	 */
	public static function escapeRegexChars( $str = '' )
	{
		$str = trim( $str );

		if( '' === $str ) {
			return $str;
		}

		$chars = ['^', '$', '(', ')', '<', '>', '.', '*', '+', '?', '[', '{', '\\', '|'];

		foreach ($chars as $k => $char) {
			$chars[$k] = '\\'.$char;
		}

		$replaced = preg_replace( '#('. join('|', $chars) .')#', '\\\${1}', $str );

		return ( null !== $replaced ) ? $replaced : $str;
	}

	/**
	 * Get the last PCRE regex execution error constant
	 *
	 * @uses get_defined_constants()
	 * @uses preg_last_error()
	 *
	 * @since 1.4.x
	 *
	 * @return string
	 */
	public static function getPregLastError()
	{
		if( !function_exists( 'preg_last_error' ) ) {
			return '';
		}

		$constants = get_defined_constants( true );

		if( !isset( $constants['pcre'] ) ) {
			return '';
		}

		$pcre = array_flip( $constants['pcre'] );
		$last = preg_last_error();

		unset( $constants );

		return ( isset( $pcre[$last] ) ) ? $pcre[$last] : '';
	}

	/**
	 * Validate for valid ip4 and ip6 ip addresses
	 *
	 * @param string $address
	 *
	 * @uses filter_var()
	 *
	 * @since 1.4.10
	 *
	 * @return boolean
	 */
	public static function isValidIpAddress( $address = '' )
	{
		// @todo check if FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6 are needed
		if( false !== filter_var( $address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
			return true;
		} elseif( false !== filter_var( $address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Check if http host is an ip address
	 *
	 * @uses ip2long()
	 *
	 * @since 2.0.15
	 *
	 * @return boolean
	 */
	public static function isRequestIpAddress()
	{
		$isIpAddress = false;
		$httpHost = $_SERVER[ 'HTTP_HOST' ];
		$parts = parse_url( $httpHost );

		if( isset( $parts[ 'host' ] ) ) {
			$isIpAddress = (bool)ip2long( $parts[ 'host' ] );
		} elseif( isset( $parts[ 'path' ] ) ) {
			$isIpAddress = (bool)ip2long( $parts[ 'path' ] );
		}

		return $isIpAddress;
	}

	/**	
	 * Get the client IP address
	 *
	 * @copyright WP Rocket
	 *
	 * @since 1.4.10
	 *
	 * @return mixed|string
	 */
	public static function getClientIp()
	{
		$headers = [
				'HTTP_CF_CONNECTING_IP', // CloudFlare
				'HTTP_CLIENT_IP',
				'HTTP_X_FORWARDED_FOR',
				'HTTP_X_FORWARDED',
				'HTTP_X_CLUSTER_CLIENT_IP',
				'HTTP_X_REAL_IP',
				'HTTP_FORWARDED_FOR',
				'HTTP_FORWARDED',
				'REMOTE_ADDR',
		];

		/**
		 * Let other filter the headers array
		 *
		 * @since 1.4.10
		 *
		 * @var array $headers
		 */
		$headers = apply_filters( 'wpie_http_ip_headers', $headers );

		foreach( $headers as $header ) {
			if ( array_key_exists( $header, $_SERVER ) ) {
				$address = explode( ',', $_SERVER[ $header ] );
				$address = end( $address );

				if ( false !== self::isValidIpAddress( $address ) ) {
					return $address;
				}
			}
		}

		return '0.0.0.0';
	}

	/**
	 * Callback to not run when a shortcode
	 *
	 * @param array $atts
	 * @param string $content
	 * @param string $tag
	 *
	 * @uses do_shortcode() if $content is not null
	 *
	 * @since 1.4.14
	 *
	 * @return string
	 */
	public static function doShortcodeNot( $atts, $content = null, $tag = '' )
	{
		if( null === $content ) {
			return '';
		} else {
			return do_shortcode( $content );
		}
	}

	/**
	 * Create a file for given location
	 *
	 * @param string $file
	 *
	 * @since 2.0
	 *
	 * @return boolean true on success or false on failure
	 */
	public static function touchFile( $file )
	{
		clearstatcache();

		$h = @fopen( $file, 'w' );
		if( false !== $h ) {
			$h = fclose( $h );
			return true;
		} else {
			return false;
		}
	}
}