: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$ip_bin = wfUtils::inet_pton($IP);
$ipHex = wfDB::binaryValueToSQLHex($ip_bin);
$row = $db->querySingleRec("select IP, ctime, failed, city, region, countryName, countryCode, lat, lon, unix_timestamp() - ctime as age from " . $locsTable . " where IP={$ipHex}");
if($row['age'] > WORDFENCE_MAX_IPLOC_AGE){
$ipHex = wfDB::binaryValueToSQLHex($row['IP']);
$db->queryWrite("delete from " . $locsTable . " where IP={$ipHex}");
$IPLocs[$ip_printable] = false;
$row['IP'] = self::inet_ntop($row['IP']);
$row['region'] = wfUtils::shouldDisplayRegion($row['countryName']) ? $row['region'] : '';
$IPLocs[$ip_printable] = $row;
if(! isset($IPLocs[$ip_printable])){
$toResolve[] = $ip_printable;
if(sizeof($toResolve) > 0){
if (wfConfig::get('enableRemoteIpLookup', true)) {
$api = new wfAPI(wfConfig::get('apiKey'), wfUtils::getWPVersion());
$freshIPs = $api->call('resolve_ips', array(), array(
'ips' => implode(',', $toResolve)
wordfence::status(2, 'error', sprintf(/* translators: Error message. */ __("Call to Wordfence API to resolve IPs failed: %s", 'wordfence'), $e->getMessage()));
require_once(__DIR__ . '/wfIpLocator.php');
$locator = wfIpLocator::getInstance();
foreach ($toResolve as $ip) {
$record = $locator->locate($ip);
$countryCode = $record->getCountryCode();
if ($countryCode !== null) {
$countryName = $record->getCountryName($locale);
if ($countryName === null)
$countryName = $countryCode;
$freshIPs[$ip] = array($countryCode, $countryName);
$freshIPs[$ip] = 'failed';
foreach($freshIPs as $IP => $value){
$IP_bin = wfUtils::inet_pton($IP);
$ipHex = wfDB::binaryValueToSQLHex($IP_bin);
$db->queryWrite("insert IGNORE into " . $locsTable . " (IP, ctime, failed) values ({$ipHex}, unix_timestamp(), 1)");
} else if(is_array($value)){
for($i = 0; $i <= 5; $i++){
//Prevent warnings in debug mode about uninitialized values
if(! isset($value[$i])){ $value[$i] = ''; }
$db->queryWrite("insert IGNORE into " . $locsTable . " (IP, ctime, failed, city, region, countryName, countryCode, lat, lon) values ({$ipHex}, unix_timestamp(), 0, '%s', '%s', '%s', '%s', %s, %s)",
'region' => wfUtils::shouldDisplayRegion($value[1]) ? $value[2] : '',
'countryName' => $value[1],
'countryCode' => $value[0],
public static function reverseLookup($IP) {
static $_memoryCache = array();
if (isset($_memoryCache[$IP])) {
return $_memoryCache[$IP];
$reverseTable = wfDB::networkTable('wfReverseCache');
$IPn = wfUtils::inet_pton($IP);
$ipHex = wfDB::binaryValueToSQLHex($IPn);
$host = $db->querySingle("select host from " . $reverseTable . " where IP={$ipHex} and unix_timestamp() - lastUpdate < %d", WORDFENCE_REVERSE_LOOKUP_CACHE_TIME);
// This function works for IPv4 or IPv6
if (function_exists('gethostbyaddr')) {
$host = @gethostbyaddr($IP);
if (filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
$ptr = implode(".", array_reverse(explode(".", $IP))) . ".in-addr.arpa";
} else if (filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
$ptr = implode(".", array_reverse(str_split(bin2hex($IPn)))) . ".ip6.arpa";
if ($ptr && function_exists('dns_get_record')) {
$host = @dns_get_record($ptr, DNS_PTR);
$host = $host[0]['target'];
$_memoryCache[$IP] = $host;
$db->queryWrite("insert into " . $reverseTable . " (IP, host, lastUpdate) values ({$ipHex}, '%s', unix_timestamp()) ON DUPLICATE KEY UPDATE host='%s', lastUpdate=unix_timestamp()", $host, $host);
$_memoryCache[$IP] = $host;
public static function errorsOff(){
self::$lastErrorReporting = @ini_get('error_reporting');
self::$lastDisplayErrors = @ini_get('display_errors');
self::iniSet('display_errors', 0);
if(class_exists('wfScan')){ wfScan::$errorHandlingOn = false; }
public static function errorsOn(){
@error_reporting(self::$lastErrorReporting);
self::iniSet('display_errors', self::$lastDisplayErrors);
if(class_exists('wfScan')){ wfScan::$errorHandlingOn = true; }
//Note this function may report files that are too big which actually are not too big but are unseekable and throw an error on fseek(). But that's intentional
public static function fileTooBig($file, &$size = false, &$handle = false){ //Deals with files > 2 gigs on 32 bit systems which are reported with the wrong size due to integer overflow
if (!@is_file($file) || !@is_readable($file)) { return false; } //Only apply to readable files
$fh = @fopen($file, 'rb');
if(! $fh){ return false; }
if(@fseek($fh, WORDFENCE_MAX_FILE_SIZE_OFFSET, SEEK_SET) === 0 && !empty(fread($fh, 1))){
} //Otherwise we couldn't seek there so it must be smaller
if ($size !== false && @fseek($fh, 0, SEEK_END) === 0) {
$size = 0; // Assume 0 if unable to determine file size
return true; //If we get an error don't scan this file, report it's too big.
public static function fileOver2Gigs($file){ //Surround calls to this func with try/catch because fseek may throw error.
$fh = @fopen($file, 'rb');
if(! $fh){ return false; }
//My throw an error so surround calls to this func with try/catch
if(@fseek($fh, $offset, SEEK_SET) === 0){
if(strlen(fread($fh, 1)) === 1){
} //Otherwise we couldn't seek there so it must be smaller
public static function countryCode2Name($code){
require(dirname(__FILE__) . '/wfBulkCountries.php'); /** @var array $wfBulkCountries */
if(isset($wfBulkCountries[$code])){
return $wfBulkCountries[$code];
public static function shouldDisplayRegion($country) {
$countries_to_show_for = array('united states', 'canada', 'australia');
return in_array(strtolower($country), $countries_to_show_for);
public static function extractBareURI($URL){
$URL = preg_replace('/^https?:\/\/[^\/]+/i', '', $URL); //strip of method and host
$URL = preg_replace('/\#.*$/', '', $URL); //strip off fragment
$URL = preg_replace('/\?.*$/', '', $URL); //strip off query string
public static function requireIpLocator() {
* This is also used in the WAF so in certain site setups (i.e. nested sites in subdirectories)
* it's possible for this to already have been loaded from a different installation of the
* plugin and hence require_once doesn't help as it's a different file path. There is no guarantee
* that the two plugin installations are the same version, so should the wfIpLocator class or any
* of its dependencies change in a manner that is not backwards compatible, this may need to be
if (!class_exists('wfIpLocator'))
require_once(__DIR__ . '/wfIpLocator.php');
public static function IP2Country($ip){
self::requireIpLocator();
return wfIpLocator::getInstance()->getCountryCode($ip);
public static function geoIPVersion() {
self::requireIpLocator();
$version = wfIpLocator::getInstance()->getDatabaseVersion();
return $version === null ? 0 : $version;
public static function siteURLRelative(){
$URL = network_site_url();
$URL = preg_replace('/^https?:\/\/[^\/]+/i', '', $URL);
$URL = rtrim($URL, '/') . '/';
public static function localHumanDate(){
return date('l jS \of F Y \a\t h:i:s A', time() + (3600 * get_option('gmt_offset')));
public static function localHumanDateShort(){
return date('D jS F \@ h:i:sA', time() + (3600 * get_option('gmt_offset')));
public static function funcEnabled($func){
if (!function_exists($func)){ return false; }
if (!is_callable($func)) { return false; }
$disabled = explode(',', ini_get('disable_functions'));
if (in_array($func, $disabled)) {
public static function iniSet($key, $val){
if(self::funcEnabled('ini_set')){
public static function doNotCache(){
header("Pragma: no-cache");
header("Cache-Control: no-cache, must-revalidate, private, max-age=0");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); //In the past
if(! defined('DONOTCACHEPAGE')){ define('DONOTCACHEPAGE', true); }
if(! defined('DONOTCACHEDB')){ define('DONOTCACHEDB', true); }
if(! defined('DONOTCDN')){ define('DONOTCDN', true); }
if(! defined('DONOTCACHEOBJECT')){ define('DONOTCACHEOBJECT', true); }
public static function isUABlocked($uaPattern){ // takes a pattern using asterisks as wildcards, turns it into regex and checks it against the visitor UA returning true if blocked
return fnmatch($uaPattern, !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', FNM_CASEFOLD);
public static function isRefererBlocked($refPattern){
return fnmatch($refPattern, !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '', FNM_CASEFOLD);
public static function error_clear_last() {
if (function_exists('error_clear_last')) {
// set error_get_last() to defined state by forcing an undefined variable error
set_error_handler('wfUtils::_resetErrorsHandler', 0);
* Logs the error given or the last PHP error to our log, rate limiting if needed.
* @param string $limiter_key
* @param null|string $error The error to log. If null, it will be the result of error_get_last
* @param int $rate Logging will only occur once per $rate seconds.
public static function check_and_log_last_error($limiter_key, $label, $error = null, $rate = 3600 /* 1 hour */) {
$error = error_get_last();
else if ($error['file'] === __FILE__) {
$error = $error['message'];
$rateKey = 'lastError_rate_' . $limiter_key;
$previousKey = 'lastError_prev_' . $limiter_key;
$previousError = wfConfig::getJSON($previousKey, array(0, false));
if ($previousError[1] != $error) {
if (wfConfig::getInt($rateKey) < time() - $rate) {
wfConfig::set($rateKey, time());
wfConfig::setJSON($previousKey, array(time(), $error));
wordfence::status(2, 'error', $label . ' ' . $error);
public static function last_error($limiter_key) {
$previousKey = 'lastError_prev_' . $limiter_key;
$previousError = wfConfig::getJSON($previousKey, array(0, false));
return wfUtils::formatLocalTime(get_option('date_format') . ' ' . get_option('time_format'), $previousError[0]) . ': ' . $previousError[1];
public static function _resetErrorsHandler($errno, $errstr, $errfile, $errline) {
public static function rangeToCIDRs($startIP, $endIP){
$start_ip_printable = wfUtils::inet_ntop($startIP);
if (filter_var($start_ip_printable, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return self::rangeToCIDRsIPv4(current(unpack('N', substr($startIP, 12, 4))), current(unpack('N', substr($endIP, 12, 4))));
$startIPBin = str_pad(wfHelperBin::bin2str($startIP), 128, '0', STR_PAD_LEFT);
$endIPBin = str_pad(wfHelperBin::bin2str($endIP), 128, '0', STR_PAD_LEFT);
while (strcmp($IPIncBin, $endIPBin) <= 0) {
while (($IPIncBin[$longNetwork - 1] == '0') && (strcmp(substr_replace($IPNetBin, '1', $longNetwork - 1, 1), $endIPBin) <= 0)) {
$IPNetBin[$longNetwork - 1] = '1';
$CIDRs[] = self::inet_ntop(str_pad(wfHelperBin::str2bin($IPIncBin), 16, "\x00", STR_PAD_LEFT)) . ($longNetwork < 128 ? '/' . $longNetwork : '');
$IPIncBin = str_pad(wfHelperBin::bin2str(wfHelperBin::addbin2bin(chr(1), wfHelperBin::str2bin($IPNetBin))), 128, '0', STR_PAD_LEFT);
public static function rangeToCIDRsIPv4($startIP, $endIP){
$startIPBin = sprintf('%032b', $startIP);
$endIPBin = sprintf('%032b', $endIP);
while(strcmp($IPIncBin, $endIPBin) <= 0){
while(($IPIncBin[$longNetwork - 1] == '0') && (strcmp(substr_replace($IPNetBin, '1', $longNetwork - 1, 1), $endIPBin) <= 0)){
$IPNetBin[$longNetwork - 1] = '1';
$CIDRs[] = long2ip(bindec($IPIncBin)) . ($longNetwork < 32 ? '/' . $longNetwork : '');
$IPIncBin = sprintf('%032b', bindec($IPNetBin) + 1);
* This is a convenience function for sending a JSON response and ensuring that execution stops after sending
* since wp_die() can be interrupted.
* @param int|null $status_code
public static function send_json($response, $status_code = null) {
wp_send_json($response, $status_code);
public static function setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly){
if(version_compare(PHP_VERSION, '5.2.0') >= 0){
@setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly);
@setcookie($name, $value, $expire, $path);
public static function isNginx(){
$serverSoft = $_SERVER['SERVER_SOFTWARE'];
if($sapi == 'fpm-fcgi' && stripos($serverSoft, 'nginx') !== false){
public static function getLastError(){
public static function hostNotExcludedFromProxy($url){
if(! defined('WP_PROXY_BYPASS_HOSTS')){
return true; //No hosts are excluded
$hosts = explode(',', WP_PROXY_BYPASS_HOSTS);
$url = preg_replace('/^https?:\/\//i', '', $url);
$url = preg_replace('/\/.*$/', '', $url);
if(strtolower(trim($h)) == $url){
public static function hasXSS($URL){
if(! preg_match('/^https?:\/\/[a-z0-9\.\-]+\/[^\':<>\"\\\]*$/i', $URL)){
public static function resolveDomainName($host, $ipVersion = null) {
if (!function_exists('dns_get_record')) {
if ($ipVersion === 4 || $ipVersion === null) {
$ips = gethostbynamel($host);
if ($ipVersion === 4 || $ipVersion === null)
$recordTypes[DNS_A] = 'ip';
if ($ipVersion === 6 || $ipVersion === null)
$recordTypes[DNS_AAAA] = 'ipv6';
foreach ($recordTypes as $type => $key) {
$records = @dns_get_record($host, $type);
if ($records !== false) {
foreach ($records as $record) {
* Expand a compressed printable representation of an IPv6 address.
public static function expandIPv6Address($ip) {
$hex = bin2hex(self::inet_pton($ip));
$ip = substr(preg_replace("/([a-f0-9]{4})/i", "$1:", $hex), 0, -1);
public static function set_html_content_type() {
public static function htmlEmail($to, $subject, $body) {
add_filter( 'wp_mail_content_type', 'wfUtils::set_html_content_type' );
$result = wp_mail($to, $subject, $body);
remove_filter( 'wp_mail_content_type', 'wfUtils::set_html_content_type' );
* @param string $readmePath
public static function hideReadme($readmePath = null) {
if ($readmePath === null) {
$readmePath = ABSPATH . 'readme.html';
if (file_exists($readmePath)) {
$readmePathInfo = pathinfo($readmePath);
require_once(ABSPATH . WPINC . '/pluggable.php');