Uname: Linux webm005.cluster107.gra.hosting.ovh.net 5.15.167-ovh-vps-grsec-zfs-classid #1 SMP Tue Sep 17 08:14:20 UTC 2024 x86_64
User: 6036 (villadal)
Group: 100 (users)
Disabled functions: NONE
Safe mode: On[ PHPinfo ]
//home/villadal/www/wp-content///plugins/wordfence/models///block/      ( Reset | Go to )
File Name: wfBlock.php
Edit
<?php

/**
 * Represents an individual block definition.
 * 
 * @property int $id
 * @property int $type One of the TYPE_* constants.
 * @property string $ip The human-readable version of the IP if applicable for the block type.
 * @property int $blockedTime The timestamp the block was created.
 * @property string $reason Description of the block.
 * @property int $lastAttempt Timestamp of the last request blocked. If never, this will be 0.
 * @property int $blockedHits Count of the number of hits blocked.
 * @property int $expiration Timestamp when the block will expire. If never, this will be 0.
 * @property mixed $parameters Variable parameters defining the block (e.g., the matchers for a pattern block).
 * 
 * @property bool $blockLogin For wfBlock::TYPE_COUNTRY only, this is whether or not to block hits to the login page.
 * @property bool $blockSite For wfBlock::TYPE_COUNTRY only, this is whether or not to block hits to the rest of the site.
 * @property array $countries For wfBlock::TYPE_COUNTRY only, this is the list of countries to block.
 * 
 * @property mixed $ipRange For wfBlock::TYPE_PATTERN only, this is the matching IP range if set.
 * @property mixed $hostname For wfBlock::TYPE_PATTERN only, this is the hostname pattern if set.
 * @property mixed $userAgent For wfBlock::TYPE_PATTERN only, this is the user agent pattern if set.
 * @property mixed $referrer For wfBlock::TYPE_PATTERN only, this is the HTTP referrer pattern if set.
 */
class wfBlock {
    
//Constants for block record types
    
const TYPE_IP_MANUAL 1//Same behavior as TYPE_IP_AUTOMATIC_PERMANENT - the reason will be overridden for public display
    
const TYPE_WFSN_TEMPORARY 2;
    const 
TYPE_COUNTRY 3;
    const 
TYPE_PATTERN 4;
    const 
TYPE_RATE_BLOCK 5;
    const 
TYPE_RATE_THROTTLE 6;
    const 
TYPE_LOCKOUT 7//Blocks login-related actions only
    
const TYPE_IP_AUTOMATIC_TEMPORARY 8//Automatic block, still temporary
    
const TYPE_IP_AUTOMATIC_PERMANENT 9//Automatic block, started as temporary but now permanent as a result of admin action
    
    //Constants to identify the match type of a block record
    
const MATCH_NONE 0;
    const 
MATCH_IP 1;
    const 
MATCH_COUNTRY_BLOCK 2;
    const 
MATCH_COUNTRY_REDIR 3;
    const 
MATCH_COUNTRY_REDIR_BYPASS 4;
    const 
MATCH_PATTERN 5;
    
    
//Duration constants
    
const DURATION_FOREVER 0;
    
    
//Constants defining the placeholder IPs for non-IP block records
    
const MARKER_COUNTRY "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x00\x02\x01";// 192.0.2.1 TEST-NET-1
    
const MARKER_PATTERN "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x00\x02\x02";// 192.0.2.2 TEST-NET-1
    
    
private $_id;
    private 
$_type false;
    private 
$_ip false;
    private 
$_blockedTime false;
    private 
$_reason false;
    private 
$_lastAttempt false;
    private 
$_blockedHits false;
    private 
$_expiration false;
    private 
$_parameters false;
    
    
/**
     * Returns the name of the storage table for the blocks.
     * 
     * @return string
     */
    
public static function blocksTable() {
        return 
wfDB::networkTable('wfBlocks7');
    }
    
    
/**
     * Returns a user-displayable name for the corresponding type constant.
     * 
     * @param int $type
     * @return string
     */
    
public static function nameForType($type) {
        switch (
$type) {
            case 
self::TYPE_IP_MANUAL:
            case 
self::TYPE_IP_AUTOMATIC_TEMPORARY:
            case 
self::TYPE_IP_AUTOMATIC_PERMANENT:
            case 
self::TYPE_WFSN_TEMPORARY:
            case 
self::TYPE_RATE_BLOCK:
                return 
__('IP Block''wordfence');
            case 
self::TYPE_RATE_THROTTLE:
                return 
__('IP Throttled''wordfence');
            case 
self::TYPE_LOCKOUT:
                return 
__('Lockout''wordfence');
            case 
self::TYPE_COUNTRY:
                return 
__('Country Block''wordfence');
            case 
self::TYPE_PATTERN:
                return 
__('Advanced Block''wordfence');
        }
        
        return 
__('Unknown''wordfence');
    }
    
    
/**
     * Returns the number of seconds for a temporary block to last by default.
     * 
     * @return int
     */
    
public static function blockDuration() {
        return (int) 
wfConfig::get('blockedTime');
    }
    
    
/**
     * Returns the number of seconds for a rate limit throttle to last by default.
     *
     * @return int
     */
    
public static function rateLimitThrottleDuration() {
        return 
60;
    }
    
    
/**
     * Returns the number of seconds for a lockout to last by default.
     *
     * @return int
     */
    
public static function lockoutDuration() {
        return (int) 
wfConfig::get('loginSec_lockoutMins') * 60;
    }
    
    
/**
     * @param string $IP Should be in dot or colon notation (127.0.0.1 or ::1)
     * @param bool $forcedWhitelistEntry If provided, returns whether or not the IP is on a forced whitelist (i.e., it's not one the user can delete).
     * @return bool
     */
    
public static function isWhitelisted($IP, &$forcedWhitelistEntry null) {
        if (
$forcedWhitelistEntry !== null) {
            
$forcedWhitelistEntry false;
        }
        
        if (
            (
defined('DOING_CRON') && DOING_CRON) || //Safe
            
(defined('WORDFENCE_SYNCING_ATTACK_DATA') && WORDFENCE_SYNCING_ATTACK_DATA//Safe as long as it will actually run since it then exits
        
) {
            
$serverIPs wfUtils::serverIPs();
            foreach (
$serverIPs as $testIP) {
                if (
wfUtils::inet_pton($IP) == wfUtils::inet_pton($testIP)) {
                    if (
$forcedWhitelistEntry !== null) {
                        
$forcedWhitelistEntry true;
                    }
                    
                    return 
true;
                }
            }
        }
        
        foreach (
wfUtils::getIPWhitelist() as $subnet) {
            if (
$subnet instanceof wfUserIPRange) {
                if (
$subnet->isIPInRange($IP)) {
                    return 
true;
                }
            } elseif (
wfUtils::subnetContainsIP($subnet$IP)) {
                if (
$forcedWhitelistEntry !== null) {
                    
$forcedWhitelistEntry true;
                }
                return 
true;
            }
        }
        
        return 
false;
    }
    
    
/**
     * Validates the payload for block creation. Returns true if valid, otherwise it'll return the first error found.
     * 
     * @param $payload
     * @return bool|string
     */
    
public static function validate($payload) {
        if (!isset(
$payload['type']) || array_search($payload['type'], array('ip-address''country''custom-pattern')) === false) { return __('Invalid block type.''wordfence'); }
        if (!isset(
$payload['duration']) || intval($payload['duration']) < 0) { return __('Invalid block duration.''wordfence'); }
        if (!isset(
$payload['reason']) || empty($payload['reason'])) { return __('A block reason must be provided.''wordfence'); }
        
        if (
$payload['type'] == 'ip-address') {
            if (!isset(
$payload['ip']) || !filter_var(trim($payload['ip']), FILTER_VALIDATE_IP) || wfUtils::inet_pton(trim($payload['ip'])) === false) { return __('Invalid IP address.''wordfence'); }
            if (
self::isWhitelisted(trim($payload['ip']))) { return wp_kses(sprintf(/* translators: Support URL */ __('This IP address is in a range of addresses that Wordfence does not block. The IP range may be internal or belong to a service that is always allowed. Allowlisting of external services can be disabled. <a href="%s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (opens in new tab)</span></a>''wordfence'), wfSupportController::supportURL(wfSupportController::ITEM_FIREWALL_WAF_OPTION_WHITELISTED_SERVICES)), array('a'=>array('href'=>array(), 'target'=>array(), 'rel'=>array()), 'span'=>array('class'=>array()))); }
        }
        else if (
$payload['type'] == 'country') {
            if (!isset(
$payload['blockLogin']) || !isset($payload['blockSite'])) { return __('Nothing selected to block.''wordfence'); }
            if (!
$payload['blockLogin'] && !$payload['blockSite']) { return __('Nothing selected to block.''wordfence'); }
            if (!isset(
$payload['countries']) || empty($payload['countries']) || !is_array($payload['countries'])) { return __('No countries selected.''wordfence'); }
            
            require(
WORDFENCE_PATH 'lib/wfBulkCountries.php'); /** @var array $wfBulkCountries */
            
foreach ($payload['countries'] as $code) {
                if (!isset(
$wfBulkCountries[$code])) {
                    return 
__('An invalid country was selected.''wordfence');
                }
            }
        }
        else if (
$payload['type'] == 'custom-pattern') {
            
$hasOne false;
            if (isset(
$payload['ipRange']) && !empty($payload['ipRange'])) {
                
$ipRange = new wfUserIPRange($payload['ipRange']);
                if (
$ipRange->isValidRange()) {
                    if (
$ipRange->isMixedRange()) {
                        return 
__('Ranges mixing IPv4 and IPv6 addresses are not supported.''wordfence');
                    }
                    
                    
$hasOne true;
                }
                else {
                    return 
__('Invalid IP range.''wordfence');
                }
            }
            if (isset(
$payload['hostname']) && !empty($payload['hostname'])) {
                if (
preg_match('/^[a-z0-9\.\*\-]+$/i'$payload['hostname'])) {
                    
$hasOne true;
                }
                else {
                    return 
__('Invalid hostname.''wordfence');
                }
            }
            if (isset(
$payload['userAgent']) && !empty($payload['userAgent'])) { $hasOne true; }
            if (isset(
$payload['referrer']) && !empty($payload['referrer'])) { $hasOne true; }
            if (!
$hasOne) { return __('No block parameters provided.''wordfence'); }
        }
        
        return 
true;
    }
    
    
/**
     * Creates the block. The $payload value is expected to have been validated prior to calling this.
     * 
     * @param $payload
     */
    
public static function create($payload) {
        
$type $payload['type'];
        
$duration max((int) $payload['duration'], 0);
        
$reason $payload['reason'];
        
        if (
$type == 'ip-address') {
            
$ip trim($payload['ip']);
            
wfBlock::createIP($reason$ip$duration);
        }
        else if (
$type == 'country') {
            
$blockLogin = !!$payload['blockLogin'];
            
$blockSite = !!$payload['blockSite'];
            
$countries array_unique($payload['countries']);
            
wfBlock::createCountry($reason$blockLogin$blockSite$countries$duration);
        }
        else if (
$type == 'custom-pattern') {
            
$ipRange '';
            if (isset(
$payload['ipRange']) && !empty($payload['ipRange'])) {
                
$ipRange = new wfUserIPRange($payload['ipRange']);
                
$ipRange $ipRange->getIPString();
            }
            
$hostname = (isset($payload['hostname']) && !empty($payload['hostname'])) ? $payload['hostname'] : '';
            
$userAgent = (isset($payload['userAgent']) && !empty($payload['userAgent'])) ? $payload['userAgent'] : '';
            
$referrer = (isset($payload['referrer']) && !empty($payload['referrer'])) ? $payload['referrer'] : '';
            
wfBlock::createPattern($reason$ipRange$hostname$userAgent$referrer$duration);
        }
    }
    
    
/**
     * Creates an IP block if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
     * 
     * @param string $reason
     * @param string $ip
     * @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
     * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
     * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
     * @param bool|int $blockedHits Optional. Defaults to 0.
     */
    
public static function createIP($reason$ip$duration self::DURATION_FOREVER$blockedTime false$lastAttempt false$blockedHits false$type self::TYPE_IP_MANUAL) {
        global 
$wpdb;
        
        if (
self::isWhitelisted($ip)) { return; }
        
        if (
$blockedTime === false) {
            
$blockedTime time();
        }
        
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
$blocksTable wfBlock::blocksTable();
        
$hasExisting $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}"$reason, ($duration $blockedTime $duration $duration), $type));
        if (!
$hasExisting) {
            
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)"$type$blockedTime$reason, (int) $lastAttempt, (int) $blockedHits, ($duration $blockedTime $duration $duration)));
            
            
wfConfig::inc('totalIPsBlocked');
        }
        
        if (
$type == self::TYPE_IP_MANUAL || $type == self::TYPE_IP_AUTOMATIC_PERMANENT) {
            
/**
             * Fires when an IP/Pattern block is created.
             *
             * @since 8.0.0
             *
             * @param string $type The type of block.
             * @param string $reason The reason for the block.
             * @param string|array $parameters The IP address being blocked for IP blocks or the pattern for pattern blocks.
             */
            
do_action('wordfence_created_ip_pattern_block'$type$reason$ip);
        }
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Creates an IP block for a WFSN response if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
     *
     * @param string $reason
     * @param string $ip
     * @param int $duration This is the number of seconds for the block to last.
     * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
     * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
     * @param bool|int $blockedHits Optional. Defaults to 0.
     */
    
public static function createWFSN($reason$ip$duration$blockedTime false$lastAttempt false$blockedHits false) {
        global 
$wpdb;
        
        if (
self::isWhitelisted($ip)) { return; }
        
        if (
$blockedTime === false) {
            
$blockedTime time();
        }
        
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
$blocksTable wfBlock::blocksTable();
        
$hasExisting $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}"$reason, ($duration $blockedTime $duration $duration), self::TYPE_WFSN_TEMPORARY));
        if (!
$hasExisting) {
            
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)"self::TYPE_WFSN_TEMPORARY$blockedTime$reason, (int) $lastAttempt, (int) $blockedHits, ($duration $blockedTime $duration $duration)));
            
            
wfConfig::inc('totalIPsBlocked');
        }
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Creates an IP block for a rate limit if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
     *
     * @param string $reason
     * @param string $ip
     * @param int $duration This is the number of seconds for the block to last.
     * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
     * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
     * @param bool|int $blockedHits Optional. Defaults to 0.
     */
    
public static function createRateBlock($reason$ip$duration$blockedTime false$lastAttempt false$blockedHits false) {
        global 
$wpdb;
        
        if (
self::isWhitelisted($ip)) { return; }
        
        if (
$blockedTime === false) {
            
$blockedTime time();
        }
        
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
$blocksTable wfBlock::blocksTable();
        
$hasExisting $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}"$reason, ($duration $blockedTime $duration $duration), self::TYPE_RATE_BLOCK));
        if (!
$hasExisting) {
            
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)"self::TYPE_RATE_BLOCK$blockedTime$reason, (int) $lastAttempt, (int) $blockedHits, ($duration $blockedTime $duration $duration)));
            
            
wfConfig::inc('totalIPsBlocked');
        }
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Creates an IP throttle for a rate limit if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
     *
     * @param string $reason
     * @param string $ip
     * @param int $duration This is the number of seconds for the block to last.
     * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
     * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
     * @param bool|int $blockedHits Optional. Defaults to 0.
     */
    
public static function createRateThrottle($reason$ip$duration$blockedTime false$lastAttempt false$blockedHits false) {
        global 
$wpdb;
        
        if (
self::isWhitelisted($ip)) { return; }
        
        if (
$blockedTime === false) {
            
$blockedTime time();
        }
        
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
$blocksTable wfBlock::blocksTable();
        
$hasExisting $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}"$reason, ($duration $blockedTime $duration $duration), self::TYPE_RATE_THROTTLE));
        if (!
$hasExisting) {
            
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)"self::TYPE_RATE_THROTTLE$blockedTime$reason, (int) $lastAttempt, (int) $blockedHits, ($duration $blockedTime $duration $duration)));
            
            
wfConfig::inc('totalIPsBlocked');
        }
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Creates a lockout if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
     *
     * @param string $reason
     * @param string $ip
     * @param int $duration This is the number of seconds for the block to last.
     * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
     * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
     * @param bool|int $blockedHits Optional. Defaults to 0.
     */
    
public static function createLockout($reason$ip$duration$blockedTime false$lastAttempt false$blockedHits false) {
        global 
$wpdb;
        
        if (
self::isWhitelisted($ip)) { return; }
        
        if (
$blockedTime === false) {
            
$blockedTime time();
        }
        
        
$blocksTable wfBlock::blocksTable();
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
$hasExisting $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}"$reason, ($duration $blockedTime $duration $duration), self::TYPE_LOCKOUT));
        if (!
$hasExisting) {
            
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)"self::TYPE_LOCKOUT$blockedTime$reason, (int) $lastAttempt, (int) $blockedHits, ($duration $blockedTime $duration $duration)));
            
            
wfConfig::inc('totalIPsLocked');
        }
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Creates a country block. The parameters are expected to have been validated and sanitized prior to calling this.
     *
     * @param string $reason
     * @param string $blockLogin
     * @param string $blockSite
     * @param string $countries
     * @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
     * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
     * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
     * @param bool|int $blockedHits Optional. Defaults to 0.
     */
    
public static function createCountry($reason$blockLogin$blockSite$countries$duration self::DURATION_FOREVER$blockedTime false$lastAttempt false$blockedHits false) {
        global 
$wpdb;
        
        if (
$blockedTime === false) {
            
$blockedTime time();
        }
        
        
$parameters = array(
            
'blockLogin' => $blockLogin 0,
            
'blockSite' => $blockSite 0,
            
'countries' => $countries,
        );
        
        
$blocksTable wfBlock::blocksTable();
        
$existing $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `type` = %d LIMIT 1"self::TYPE_COUNTRY), ARRAY_A);
        
$before = array(
            
'parameters' => null,
            
'bypass' => array(
                
'cbl_loggedInBlocked' => wfConfig::get('cbl_loggedInBlocked'false),
                
'cbl_action' => wfConfig::get('cbl_action'),
                
'cbl_redirURL' => wfConfig::get('cbl_redirURL'''),
                
'cbl_bypassRedirURL' => wfConfig::get('cbl_bypassRedirURL'''),
                
'cbl_bypassRedirDest' => wfConfig::get('cbl_bypassRedirDest'''),
                
'cbl_bypassViewURL' => wfConfig::get('cbl_bypassViewURL'''),
            ),
        );
        
$after $before;
        
$after['parameters'] = $parameters;
        if (
$existing) {
            
$before['parameters'] = @json_decode($existing['parameters'], true);
            if (!
is_array($before['parameters'])) { $before['parameters'] = array(); }
            
$wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `parameters` = %s WHERE `id` = %d"$reasonjson_encode($parameters), $existing['id']));
        }
        else {
            
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, %s, %d, %s, %d, %d, %d, %s)"self::TYPE_COUNTRYself::MARKER_COUNTRY$blockedTime$reason, (int) $lastAttempt, (int) $blockedHits, ($duration $blockedTime $duration $duration), json_encode($parameters)));
        }
        
        
/**
         * Fires when the country blocking rule is updated.
         *
         * @since 8.0.0
         *
         * @param array $before {
         *         The country block configuration before the change
         * 
         *         @type array $parameters The parameters of the country block.
         *         @type array $bypass {
         *             The assorted bypass settings for country blocking.
         * 
         *             @type bool $cbl_loggedInBlocked Block countries even if there is a valid logged-in user for the request
         *             @type string $cbl_action Action taken when a request is received from a blocked country
         *             @type string $cbl_redirURL URL destination when $cbl_action is `redir`
         *             @type string $cbl_bypassRedirURL If a visitor hits this URL
         *             @type string $cbl_bypassRedirDest then redirect to this URL and set a cookie that will bypass all country blocking
         *             @type string $cbl_bypassViewURL If a user currently not blocked hits this URL, then set a cookie that will bypass country blocking in the future even if visiting from a blocked country
         *         }
         * }
         * @param array $after The country block configuration after the change, same structure as $before
         */
        
do_action('wordfence_updated_country_blocking'$before$after);
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Creates a pattern block. The parameters are expected to have been validated and sanitized prior to calling this.
     * 
     * @param string $reason
     * @param string $ipRange
     * @param string $hostname
     * @param string $userAgent
     * @param string $referrer
     * @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
     * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
     * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
     * @param bool|int $blockedHits Optional. Defaults to 0.
     */
    
public static function createPattern($reason$ipRange$hostname$userAgent$referrer$duration self::DURATION_FOREVER$blockedTime false$lastAttempt false$blockedHits false) {
        global 
$wpdb;
        
        if (
$blockedTime === false) {
            
$blockedTime time();
        }
        
        
$parameters = array(
            
'ipRange' => $ipRange,
            
'hostname' => $hostname,
            
'userAgent' => $userAgent,
            
'referrer' => $referrer,
        );
        
        
$blocksTable wfBlock::blocksTable();
        
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, %s, %d, %s, %d, %d, %d, %s)"self::TYPE_PATTERNself::MARKER_PATTERN$blockedTime$reason, (int) $lastAttempt, (int) $blockedHits, ($duration $blockedTime $duration $duration), json_encode($parameters)));
        
        
/**
         * @see wfBlock::createIP()
         */
        
do_action('wordfence_created_ip_pattern_block'self::TYPE_PATTERN$reason$parameters);
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Removes all expired blocks.
     */
    
public static function vacuum() {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$wpdb->query("DELETE FROM `{$blocksTable}` WHERE `expiration` <= UNIX_TIMESTAMP() AND `expiration` != " self::DURATION_FOREVER);
    }
    
    
/**
     * Imports all valid blocks in $blocks. If $replaceExisting is true, this will remove all permanent blocks prior to the import.
     * 
     * @param array $blocks
     * @param bool $replaceExisting
     */
    
public static function importBlocks($blocks$replaceExisting true) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
        if (
$replaceExisting) {
            
$removing self::_recordsFromRows($wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `expiration` = " self::DURATION_FOREVERARRAY_A));
            
self::removeMultiple($removingtrue);
        }
        
        foreach (
$blocks as $b) {
            
self::_importBlock($b);
        }
        
        if (!
WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
            
wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
        }
    }
    
    
/**
     * Validates the block import record and inserts it if valid. This validation is identical to what is applied to adding one through the UI.
     * 
     * @param array $b
     * @return bool
     */
    
private static function _importBlock($b) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
        if (!isset(
$b['type']) || !isset($b['IP']) || !isset($b['blockedTime']) || !isset($b['reason']) || !isset($b['lastAttempt']) || !isset($b['blockedHits'])) { return false; }
        if (empty(
$b['IP']) || empty($b['reason'])) { return false; }
        
        
$ip wfUtils::inet_ntop(wfUtils::hex2bin($b['IP']));
        if (!
wfUtils::isValidIP($ip)) { return false; }
        
        switch (
$b['type']) {
            case 
self::TYPE_IP_MANUAL:
            case 
self::TYPE_IP_AUTOMATIC_TEMPORARY:
            case 
self::TYPE_IP_AUTOMATIC_PERMANENT:
            case 
self::TYPE_WFSN_TEMPORARY:
            case 
self::TYPE_RATE_BLOCK:
            case 
self::TYPE_RATE_THROTTLE:
            case 
self::TYPE_LOCKOUT:
                if (
self::isWhitelisted($ip)) { return false; }

                if (
$b['type'] == self::TYPE_IP_MANUAL || $b['type'] == self::TYPE_IP_AUTOMATIC_PERMANENT) {
                    
/**
                     * @see wfBlock::createIP()
                     */
                    
do_action('wordfence_created_ip_pattern_block'$b['type'], $b['reason'], $ip);
                }
            
                
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
                return 
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", (int) $b['type'], (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVER)) !== false;
            case 
self::TYPE_COUNTRY:
                if (!isset(
$b['parameters'])) { return false; }
                if (
wfUtils::inet_pton($ip) != self::MARKER_COUNTRY) { return false; }
                
$parameters = @json_decode($b['parameters'], true);
                if (!
is_array($parameters)) { $parameters = array(); }
                if (!isset(
$parameters['blockLogin']) || !isset($parameters['blockSite']) || !isset($parameters['countries'])) { return false; }
                
$parameters['blockLogin'] = wfUtils::truthyToInt($parameters['blockLogin']);
                
$parameters['blockSite'] = wfUtils::truthyToInt($parameters['blockSite']);
                
                require(
WORDFENCE_PATH 'lib/wfBulkCountries.php'); /** @var array $wfBulkCountries */
                
foreach ($parameters['countries'] as $code) {
                    if (!isset(
$wfBulkCountries[$code])) {
                        return 
false;
                    }
                }
                
                
$parameters = array('blockLogin' => $parameters['blockLogin'], 'blockSite' => $parameters['blockSite'], 'countries' => $parameters['countries']);
            
                
$before = array(
                    
'parameters' => null,
                    
'bypass' => array(
                        
'cbl_loggedInBlocked' => wfConfig::get('cbl_loggedInBlocked'false),
                        
'cbl_action' => wfConfig::get('cbl_action'),
                        
'cbl_redirURL' => wfConfig::get('cbl_redirURL'''),
                        
'cbl_bypassRedirURL' => wfConfig::get('cbl_bypassRedirURL'''),
                        
'cbl_bypassRedirDest' => wfConfig::get('cbl_bypassRedirDest'''),
                        
'cbl_bypassViewURL' => wfConfig::get('cbl_bypassViewURL'''),
                    ),
                );
                
$after $before;
                
$after['parameters'] = $parameters;
                
                
/**
                 * @see wfBlock::createCountry()
                 */
                
do_action('wordfence_updated_country_blocking'$before$after);
                
                
$ipHex wfDB::binaryValueToSQLHex(self::MARKER_COUNTRY);
                return 
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, %s)"self::TYPE_COUNTRY, (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVERjson_encode($parameters))) !== false;
            case 
self::TYPE_PATTERN:
                if (!isset(
$b['parameters'])) { return false; }
                if (
wfUtils::inet_pton($ip) != self::MARKER_PATTERN) { return false; }
                
$parameters = @json_decode($b['parameters'], true);
                if (!
is_array($parameters)) { $parameters = array(); }
                if (!isset(
$parameters['ipRange']) || !isset($parameters['hostname']) || !isset($parameters['userAgent']) || !isset($parameters['referrer'])) { return false; }
                
                
$hasOne false;
                if (!empty(
$parameters['ipRange'])) {
                    
$ipRange = new wfUserIPRange($parameters['ipRange']);
                    if (
$ipRange->isValidRange()) {
                        if (
$ipRange->isMixedRange()) {
                            return 
false;
                        }
                        
                        
$hasOne true;
                    }
                    else {
                        return 
false;
                    }
                }
                if (!empty(
$parameters['hostname'])) {
                    if (
preg_match('/^[a-z0-9\.\*\-]+$/i'$parameters['hostname'])) {
                        
$hasOne true;
                    }
                    else {
                        return 
false;
                    }
                }
                if (!empty(
$parameters['userAgent'])) { $hasOne true; }
                if (!empty(
$parameters['referrer'])) { $hasOne true; }
                if (!
$hasOne) { return false; }
                
                
$ipRange '';
                if (!empty(
$parameters['ipRange'])) {
                    
$ipRange = new wfUserIPRange($parameters['ipRange']);
                    
$ipRange $ipRange->getIPString();
                }
                
$parameters = array(
                    
'ipRange' => $ipRange,
                    
'hostname' => $parameters['hostname'],
                    
'userAgent' => $parameters['userAgent'],
                    
'referrer' => $parameters['referrer'],
                );
                
                
/**
                 * @see wfBlock::createIP()
                 */
                
do_action('wordfence_created_ip_pattern_block'$b['type'], $b['reason'], $parameters);
                
                
$ipHex wfDB::binaryValueToSQLHex(self::MARKER_PATTERN);
                return 
$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, %s)"self::TYPE_PATTERN, (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVERjson_encode($parameters))) !== false;
        }
        
        return 
false;
    }
    
    
/**
     * Returns an array suitable for JSON output of all permanent blocks.
     * 
     * @return array
     */
    
public static function exportBlocks() {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$query "SELECT `type`, HEX(`IP`) AS `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `parameters` FROM `{$blocksTable}` WHERE `expiration` = " self::DURATION_FOREVER;
        
$rows $wpdb->get_results($queryARRAY_A);
        return 
$rows;
    }
    
    
/**
     * Returns all unexpired blocks (including lockouts by default), optionally only of the specified types. These are sorted descending by the time created.
     * 
     * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
     * @param array $ofTypes An optional array of block types to restrict the returned array of blocks to.
     * @param int $offset The offset to start the result fetch at.
     * @param int $limit The maximum number of results to return. -1 for all.
     * @param string $sortColumn The column to sort by.
     * @param string $sortDirection The direction to sort.
     * @param string $filter An optional value to filter by.
     * @return wfBlock[]
     */
    
public static function allBlocks($prefetch false$ofTypes = array(), $offset 0$limit = -1$sortColumn 'type'$sortDirection 'ascending'$filter '') {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$columns '`id`';
        if (
$prefetch) {
            
$columns '*';
        }
        
        
$sort 'typeSort';
        switch (
$sortColumn) { //Match the display table column to the corresponding schema column
            
case 'type':
                
//Use default;
                
break;
            case 
'detail':
                
$sort 'detailSort';
                break;
            case 
'ruleAdded':
                
$sort 'blockedTime';
                break;
            case 
'reason':
                
$sort 'reason';
                break;
            case 
'expiration':
                
$sort 'expiration';
                break;
            case 
'blockCount':
                
$sort 'blockedHits';
                break;
            case 
'lastAttempt':
                
$sort 'lastAttempt';
                break;
        }
        
        
$order 'ASC';
        if (
$sortDirection == 'descending') {
            
$order 'DESC';
        }
        
        
$query "SELECT {$columns}, CASE 
WHEN `type` = " 
self::TYPE_COUNTRY " THEN 0
WHEN `type` = " 
self::TYPE_PATTERN " THEN 1
WHEN `type` = " 
self::TYPE_LOCKOUT " THEN 2
WHEN `type` = " 
self::TYPE_RATE_THROTTLE " THEN 3
WHEN `type` = " 
self::TYPE_RATE_BLOCK " THEN 4
WHEN `type` = " 
self::TYPE_IP_AUTOMATIC_PERMANENT " THEN 5
WHEN `type` = " 
self::TYPE_IP_AUTOMATIC_TEMPORARY " THEN 6
WHEN `type` = " 
self::TYPE_WFSN_TEMPORARY " THEN 7
WHEN `type` = " 
self::TYPE_IP_MANUAL " THEN 8
ELSE 9999
END AS `typeSort`, CASE 
WHEN `type` = " 
self::TYPE_COUNTRY " THEN `parameters`
WHEN `type` = " 
self::TYPE_PATTERN " THEN `parameters`
WHEN `type` = " 
self::TYPE_IP_MANUAL " THEN `IP`
WHEN `type` = " 
self::TYPE_IP_AUTOMATIC_PERMANENT " THEN `IP`
WHEN `type` = " 
self::TYPE_RATE_BLOCK " THEN `IP`
WHEN `type` = " 
self::TYPE_RATE_THROTTLE " THEN `IP`
WHEN `type` = " 
self::TYPE_LOCKOUT " THEN `IP`
WHEN `type` = " 
self::TYPE_WFSN_TEMPORARY " THEN `IP`
WHEN `type` = " 
self::TYPE_IP_AUTOMATIC_TEMPORARY " THEN `IP`
ELSE 9999
END AS `detailSort`
 FROM `
{$blocksTable}` WHERE ";
        if (!empty(
$ofTypes)) {
            
$sanitizedTypes array_map('intval'$ofTypes);
            
$query .= "`type` IN (" implode(', '$sanitizedTypes) . ') AND ';
        }
        
$query .= '(`expiration` = ' self::DURATION_FOREVER " OR `expiration` > UNIX_TIMESTAMP()) ORDER BY `{$sort}{$order}, `id` DESC";
        
        if (
$limit > -1) {
            
$offset = (int) $offset;
            
$limit = (int) $limit;
            
$query .= " LIMIT {$offset},{$limit}";
        }
        
        
$rows $wpdb->get_results($queryARRAY_A);
        
$result = array();
        foreach (
$rows as $r) {
            if (
$prefetch) {
                if (
$r['type'] == self::TYPE_COUNTRY || $r['type'] == self::TYPE_PATTERN) {
                    
$ip null;
                }
                else {
                    
$ip wfUtils::inet_ntop($r['IP']);
                }
                
                
$parameters null;
                if (
$r['type'] == self::TYPE_PATTERN || $r['type'] == self::TYPE_COUNTRY) {
                    
$parameters = @json_decode($r['parameters'], true);
                    if (!
is_array($parameters)) { $parameters = array(); }
                }
                
                
$result[] = new wfBlock($r['id'], $r['type'], $ip$r['blockedTime'], $r['reason'], $r['lastAttempt'], $r['blockedHits'], $r['expiration'], $parameters);
            }
            else {
                
$result[] = new wfBlock($r['id']);
            }
        }
        
        return 
$result;
    }
    
    
/**
     * Functions identically to wfBlock::allBlocks except that it filters the result. The filtering is done within PHP rather than MySQL, so this will impose a performance penalty and should only
     * be used when filtering is actually wanted.
     * 
     * @param bool $prefetch
     * @param array $ofTypes
     * @param int $offset
     * @param int $limit
     * @param string $sortColumn
     * @param string $sortDirection
     * @param string $filter
     * @return wfBlock[]
     */
    
public static function filteredBlocks($prefetch false$ofTypes = array(), $offset 0$limit = -1$sortColumn 'type'$sortDirection 'ascending'$filter '') {
        
$filter trim($filter);
        
$matchType '';
        
$matchValue '';
        if (empty(
$filter)) {
            return 
self::allBlocks($prefetch$ofTypes$offset$limit$sortColumn$sortDirection);
        }
        else if (
wfUtils::isValidIP($filter)) { //e.g., 4.5.6.7, ffe0::, ::0
            
$matchType 'ip';
            
$matchValue wfUtils::inet_ntop(wfUtils::inet_pton($filter));
        }
        
        if (empty(
$matchType) && preg_match('/^(?:[0-9]+|\*)\.(?:(?:[0-9]+|\*)\.(?!$))*(?:(?:[0-9]+|\*))?$/'trim($filter'.'))) { //e.g., possible wildcard IPv4 like 4.5.*
            
$components explode('.'trim($filter'.'));
            if (
count($components) <= 4) {
                
$components array_pad($components4'*');
                
$matchType 'ipregex';
                
$matchValue '^';
                foreach (
$components as $c) {
                    if (empty(
$c) || $c == '*') {
                        
$matchValue .= '\d+';
                    }
                    else {
                        
$matchValue .= (int) $c;
                    }
                    
                    
$matchValue .= '\.';
                }
                
$matchValue substr($matchValue0, -2);
                
$matchValue .= '$';
            }
        }
        
        if (empty(
$matchType) && preg_match('/^(?:[0-9a-f]+\:)(?:[0-9a-f]+\:|\*){1,2}(?:[0-9a-f]+|\*)?$/i'$filter)) { //e.g., possible wildcard IPv6 like ffe0:*
            
$components explode(':'$filter);
            
$matchType 'ipregex';
            
$matchValue '^';
            for (
$i 0$i 4$i++) {
                if (isset(
$components[$i])) {
                    
$matchValue .= strtoupper(str_pad(dechex($components[$i]), 4'0'STR_PAD_LEFT));
                }
                else {
                    
$matchValue .= '[0-9a-f]{4}';
                }
                
$matchValue .= ':';
            }
            
$matchValue substr($matchValue0, -1);
            
$matchValue .= '$';
        }
        
        if (empty(
$matchType)) {
            
$matchType 'literal';
            
$matchValue $filter;
        }
        
        
$offsetProcessed 0;
        
$limitProcessed 0;
        
        
$returnBlocks = array();
        for (
$i 0true$i += WORDFENCE_BLOCKED_IPS_PER_PAGE) {
            
$blocks wfBlock::allBlocks(true$ofTypes$iWORDFENCE_BLOCKED_IPS_PER_PAGE$sortColumn$sortDirection);
            if (empty(
$blocks)) {
                break;
            }
            
            foreach (
$blocks as $b) {
                
$include false;
                
                if (
stripos($b->reason$filter) !== false) {
                    
$include true;
                }
                
                if (!
$include && $b->type == self::TYPE_PATTERN) {
                    if (
stripos($b->hostname$filter) !== false) { $include true; }
                    else if (
stripos($b->userAgent$filter) !== false) { $include true; }
                    else if (
stripos($b->referrer$filter) !== false) { $include true; }
                    else if (
stripos($b->ipRange$filter) !== false) { $include true; }
                }
                
                if (!
$include && stripos(self::nameForType($b->type), $filter) !== false) {
                    
$include true;
                }
                
                if (!
$include) {
                    switch (
$matchType) {
                        case 
'ip':
                            if (
$b->matchRequest($matchValue'''') != self::MATCH_NONE) {
                                
$include true;
                            }
                            else if (
$b->type == self::TYPE_LOCKOUT && wfUtils::inet_pton($matchValue) == wfUtils::inet_pton($b->ip)) {
                                
$include true;
                            }
                            break;
                        case 
'ipregex':
                            if (
preg_match('/' $matchValue '/i'$b->ip)) {
                                
$include true;
                            }
                            break;
                        case 
'literal':
                            
//Already checked above
                            
break;
                    }
                }
                
                if (
$include) {
                    if (
$offsetProcessed $offset) { //Still searching for the start offset
                        
$offsetProcessed++;
                        continue;
                    }
                    
                    
$returnBlocks[] = $b;
                    
$limitProcessed++;
                }
                
                if (
$limit != -&& $limitProcessed >= $limit) {
                    return 
$returnBlocks;
                }
            }
        }
        
        return 
$returnBlocks;
    }
    
    
/**
     * Returns all unexpired blocks of types wfBlock::TYPE_IP_MANUAL, wfBlock::TYPE_IP_AUTOMATIC_TEMPORARY, wfBlock::TYPE_IP_AUTOMATIC_PERMANENT, wfBlock::TYPE_WFSN_TEMPORARY, wfBlock::TYPE_RATE_BLOCK, and wfBlock::TYPE_RATE_THROTTLE.
     *
     * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
     * @return wfBlock[]
     */
    
public static function ipBlocks($prefetch false) {
        return 
self::allBlocks($prefetch, array(self::TYPE_IP_MANUALself::TYPE_IP_AUTOMATIC_TEMPORARYself::TYPE_IP_AUTOMATIC_PERMANENTself::TYPE_WFSN_TEMPORARYself::TYPE_RATE_BLOCKself::TYPE_RATE_THROTTLE));
    }
    
    
/**
     * Finds an IP block matching the given IP, returning it if found. Returns false if none are found.
     * 
     * @param string $ip
     * @return bool|wfBlock
     */
    
public static function findIPBlock($ip) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
        
$query "SELECT * FROM `{$blocksTable}` WHERE ";
        
        
$ofTypes = array(self::TYPE_IP_MANUALself::TYPE_IP_AUTOMATIC_TEMPORARYself::TYPE_IP_AUTOMATIC_PERMANENTself::TYPE_WFSN_TEMPORARYself::TYPE_RATE_BLOCKself::TYPE_RATE_THROTTLE);
        
$query .= "`type` IN (" implode(', '$ofTypes) . ') AND ';
        
$query .= "`IP` = {$ipHex} AND ";
        
$query .= '(`expiration` = ' self::DURATION_FOREVER ' OR `expiration` > UNIX_TIMESTAMP()) ORDER BY `blockedTime` DESC LIMIT 1';
        
        
$r $wpdb->get_row($queryARRAY_A);
        if (
is_array($r)) {
            
$ip wfUtils::inet_ntop($r['IP']);
            return 
self::_recordFromRow($r);
        }
        return 
false;
    }
    
    
/**
     * Returns all unexpired blocks of type wfBlock::TYPE_COUNTRY.
     *
     * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
     * @return wfBlock[]
     */
    
public static function countryBlocks($prefetch false) {
        return 
self::allBlocks($prefetch, array(self::TYPE_COUNTRY));
    }
    
    
/**
     * Returns whether or not there is a country block rule.
     * 
     * @return bool
     */
    
public static function hasCountryBlock() {
        
$countryBlocks self::countryBlocks();
        return !empty(
$countryBlocks);
    }
    
    
/**
     * Returns the value for the country blocking bypass cookie.
     *
     * @return string
     */
    
public static function countryBlockingBypassCookieValue() {
        
$val wfConfig::get('cbl_cookieVal'false);
        if (!
$val) {
            
$val uniqid();
            
wfConfig::set('cbl_cookieVal'$val);
        }
        return 
$val;
    }
    
    
/**
     * Returns all unexpired blocks of type wfBlock::TYPE_PATTERN.
     * 
     * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
     * @return wfBlock[]
     */
    
public static function patternBlocks($prefetch false) {
        return 
self::allBlocks($prefetch, array(self::TYPE_PATTERN));
    }
    
    
/**
     * Returns all unexpired lockouts (type wfBlock::TYPE_LOCKOUT).
     *
     * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
     * @return wfBlock[]
     */
    
public static function lockouts($prefetch false) {
        return 
self::allBlocks($prefetch, array(self::TYPE_LOCKOUT));
    }
    
    
/**
     * Returns the lockout record for the given IP if it exists.
     * 
     * @param string $ip
     * @return bool|wfBlock
     */
    
public static function lockoutForIP($ip) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
        
$row $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex} AND `type` = %d AND (`expiration` = %d OR `expiration` > UNIX_TIMESTAMP())",  self::TYPE_LOCKOUTself::DURATION_FOREVER), ARRAY_A);
        if (
$row) {
            return 
self::_recordFromRow($row);
        }
        
        return 
false;
    }
    
    
/**
     * Removes all blocks whose ID is in the given array.
     * 
     * @param array $blockIDs
     * @param bool $retrieve if true, fetch and return the deleted rows
     * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
     * @return bool|array true(or an array of blocks, if $retrieve is specified) or false on failure
     */
    
public static function removeBlockIDs($blockIDs$retrieve false$notify true) {
        
$blockIDs array_map('intval'$blockIDs);
        
$blocks self::_recordsFromRows($blockIDs);
        
$result self::removeMultiple($blocks$notify);
        if (
$retrieve && $result) {
            return 
$result;
        }
        
        return !!
$result;
    }
    
    
/**
     * Removes all IP blocks (i.e., manual, wfsn, or rate limited)
     *
     * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
     */
    
public static function removeAllIPBlocks($notify true) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$rows $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `type` IN (" implode(', ', array(self::TYPE_IP_MANUALself::TYPE_IP_AUTOMATIC_TEMPORARYself::TYPE_IP_AUTOMATIC_PERMANENTself::TYPE_WFSN_TEMPORARYself::TYPE_RATE_BLOCKself::TYPE_RATE_THROTTLEself::TYPE_LOCKOUT)) . ")"ARRAY_A);
        
$blocks self::_recordsFromRows($rows);
        
self::removeMultiple($blocks$notify);
    }
    
    
/**
     * Removes all country blocks
     *
     * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
     */
    
public static function removeAllCountryBlocks($notify true) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$rows $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `type` IN (" implode(', ', array(self::TYPE_COUNTRY)) . ")"ARRAY_A);
        
$blocks self::_recordsFromRows($rows);
        
self::removeMultiple($blocks$notify);
    }
    
    
/**
     * Removes all blocks that were created by WFSN responses.
     * 
     * Note: if this ever becomes called by a manual user action, it should be refactored to call 
     * self::removeMultiple() in order to dispatch the relevant event.
     */
    
public static function removeTemporaryWFSNBlocks() {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$wpdb->query($wpdb->prepare("DELETE FROM `{$blocksTable}` WHERE `type` = %d"self::TYPE_WFSN_TEMPORARY));
    }
    
    
/**
     * Converts all blocks to non-expiring whose ID is in the given array.
     * 
     * @param array $blockIDs
     */
    
public static function makePermanentBlockIDs($blockIDs) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
        
//TODO: revise this if we support user-customizable durations
        
$supportedTypes = array(
            
self::TYPE_WFSN_TEMPORARY,
            
self::TYPE_RATE_BLOCK,
            
self::TYPE_RATE_THROTTLE,
            
self::TYPE_LOCKOUT,
            
self::TYPE_IP_AUTOMATIC_TEMPORARY,
        );
        
        
$blockIDs array_map('intval'$blockIDs);
        
$query $wpdb->prepare("UPDATE `{$blocksTable}` SET `expiration` = %d, `type` = %d WHERE `id` IN (" implode(', '$blockIDs) . ") AND `type` IN (" implode(', '$supportedTypes) . ") AND (`expiration` > UNIX_TIMESTAMP())"self::DURATION_FOREVERself::TYPE_IP_AUTOMATIC_PERMANENT);
        
$wpdb->query($query);
        
        
$supportedTypes = array(
            
self::TYPE_IP_MANUAL,
        );
        
        
$blockIDs array_map('intval'$blockIDs);
        
$query $wpdb->prepare("UPDATE `{$blocksTable}` SET `expiration` = %d, `type` = %d WHERE `id` IN (" implode(', '$blockIDs) . ") AND `type` IN (" implode(', '$supportedTypes) . ") AND (`expiration` > UNIX_TIMESTAMP())"self::DURATION_FOREVERself::TYPE_IP_MANUAL);
        
$wpdb->query($query);
    }
    
    
/**
     * Removes all specific IP blocks and lockouts that can result in the given IP being blocked.
     * 
     * @param string $ip
     * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
     */
    
public static function unblockIP($ip$notify true) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
$rows $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex}"ARRAY_A);
        
$blocks self::_recordsFromRows($rows);
        
self::removeMultiple($blocks$notify);
    }
    
    
/**
     * Removes all lockouts that can result in the given IP being blocked.
     *
     * @param string $ip
     * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
     */
    
public static function unlockOutIP($ip$notify true) {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$ipHex wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
        
$rows $wpdb->get_results($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex} AND `type` = %d"self::TYPE_LOCKOUT), ARRAY_A);
        
$blocks self::_recordsFromRows($rows);
        
self::removeMultiple($blocks$notify);
    }
    
    
/**
     * Internal function to convert a raw query row result into a populated wfBlock instance. $row is expected to be an
     * associative array.
     * 
     * @param array $row
     * @return mixed
     */
    
private static function _recordFromRow($row) {
        
$records self::_recordsFromRows(array($row));
        return 
$records[0];
    }
    
    
/**
     * Internal function to convert an array of raw query row results to an array of populated wfBlock instances. $rows
     * is expected to be an array of integer IDs or an array of associative arrays.
     * 
     * @param array[]|int[] $rows
     * @return array
     */
    
private static function _recordsFromRows($rows) {
        
$records = array();
        foreach (
$rows as $r) {
            if (
$r instanceof stdClass) {
                
$r = (array) $r;
            }
            
            if (
is_array($r)) {
                
$b = new wfBlock($r['id']);
                
$b->_populateData($r);
            }
            else {
                
$b = new wfBlock($r);
            }
            
$records[] = $b;
        }
        return 
$records;
    }
    
    
/**
     * Constructs a wfBlock instance. This _does not_ create a new record in the table, only fetches or updates an existing one.
     * 
     * @param $id
     * @param bool $type
     * @param bool $ip
     * @param bool $blockedTime
     * @param bool $reason
     * @param bool $lastAttempt
     * @param bool $blockedHits
     * @param bool $expiration
     * @param bool $parameters
     */
    
public function __construct($id$type false$ip false$blockedTime false$reason false$lastAttempt false$blockedHits false$expiration false$parameters false) {
        
$this->_id $id;
        
$this->_type $type;
        
$this->_ip $ip;
        
$this->_blockedTime $blockedTime;
        
$this->_reason $reason;
        
$this->_lastAttempt $lastAttempt;
        
$this->_blockedHits $blockedHits;
        
$this->_expiration $expiration;
        
$this->_parameters $parameters;
    }
    
    public function 
__get($key) {
        switch (
$key) {
            case 
'id':
                return 
$this->_id;
            case 
'type':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_type;
            case 
'IP':
            case 
'ip':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_ip;
            case 
'blockedTime':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_blockedTime;
            case 
'reason':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_reason;
            case 
'lastAttempt':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_lastAttempt;
            case 
'blockedHits':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_blockedHits;
            case 
'expiration':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_expiration;
            case 
'parameters':
                if (
$this->_type === false) { $this->_fetch(); }
                return 
$this->_parameters;
                
            
//Country
            
case 'blockLogin':
                if (
$this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
                return 
$this->parameters['blockLogin'];
            case 
'blockSite':
                if (
$this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
                return 
$this->parameters['blockSite'];
            case 
'countries':
                if (
$this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
                return 
$this->parameters['countries'];
                
            
//Pattern
            
case 'ipRange':
                if (
$this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
                return 
$this->parameters['ipRange'];
            case 
'hostname':
                if (
$this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
                return 
$this->parameters['hostname'];
            case 
'userAgent':
                if (
$this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
                return 
$this->parameters['userAgent'];
            case 
'referrer':
                if (
$this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
                return 
$this->parameters['referrer'];
        }
        
        throw new 
OutOfBoundsException("{$key} is not a valid property");
    }
    
    public function 
__isset($key) {
        switch (
$key) {
            case 
'id':
            case 
'type':
            case 
'ip':
            case 
'blockedTime':
            case 
'reason':
            case 
'lastAttempt':
            case 
'blockedHits':
            case 
'expiration':
                return 
true;
            case 
'parameters':
                if (
$this->_type === false) { $this->_fetch(); }
                return !empty(
$this->_parameters);
            
            
//Country
            
case 'blockLogin':
                if (
$this->type != self::TYPE_COUNTRY) { return false; }
                return !empty(
$this->parameters['blockLogin']);
            case 
'blockSite':
                if (
$this->type != self::TYPE_COUNTRY) { return false; }
                return !empty(
$this->parameters['blockSite']);
            case 
'countries':
                if (
$this->type != self::TYPE_COUNTRY) { return false; }
                return !empty(
$this->parameters['countries']);
            
            
//Pattern
            
case 'ipRange':
                if (
$this->type != self::TYPE_PATTERN) { return false; }
                return !empty(
$this->parameters['ipRange']);
            case 
'hostname':
                if (
$this->type != self::TYPE_PATTERN) { return false; }
                return !empty(
$this->parameters['hostname']);
            case 
'userAgent':
                if (
$this->type != self::TYPE_PATTERN) { return false; }
                return !empty(
$this->parameters['userAgent']);
            case 
'referrer':
                if (
$this->type != self::TYPE_PATTERN) { return false; }
                return !empty(
$this->parameters['referrer']);
        }
        
        return 
false;
    }
    
    
/**
     * Fetches the record for the block from the database and populates the instance variables.
     */
    
private function _fetch() {
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$row $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `id` = %d"$this->id), ARRAY_A);
        if (
$row !== null) {
            
$this->_populateData($row);
        }
    }
    
    
/**
     * Populates the instance data from the given $row.
     * 
     * @param array $row
     */
    
private function _populateData($row) {
        
$this->_type $row['type'];
        
        
$ip $row['IP'];
        if (
$ip == self::MARKER_COUNTRY || $ip == self::MARKER_PATTERN) {
            
$this->_ip null;
        }
        else {
            
$this->_ip wfUtils::inet_ntop($ip);
        }
        
        
$this->_blockedTime $row['blockedTime'];
        
$this->_reason $row['reason'];
        
$this->_lastAttempt $row['lastAttempt'];
        
$this->_blockedHits $row['blockedHits'];
        
$this->_expiration $row['expiration'];
        
        
$parameters $row['parameters'];
        if (
$parameters === null) {
            
$this->_parameters null;
        }
        else {
            
$this->_parameters = @json_decode($parameterstrue);
            if (!
is_array($this->_parameters)) { $this->_parameters = array(); }
        }
    }
    
    
/**
     * Tests the block parameters against the given request. If matched, this will return the corresponding wfBlock::MATCH_
     * constant. If not, it will return wfBlock::MATCH_NONE.
     * 
     * @param $ip
     * @param $userAgent
     * @param $referrer
     * @return int
     */
    
public function matchRequest($ip$userAgent$referrer) {
        switch (
$this->type) {
            case 
self::TYPE_IP_MANUAL:
            case 
self::TYPE_IP_AUTOMATIC_TEMPORARY:
            case 
self::TYPE_IP_AUTOMATIC_PERMANENT:
            case 
self::TYPE_WFSN_TEMPORARY:
            case 
self::TYPE_RATE_BLOCK:
            case 
self::TYPE_RATE_THROTTLE:
                if (
wfUtils::inet_pton($ip) == wfUtils::inet_pton($this->ip))
                {
                    return 
self::MATCH_IP;
                }
                break;
            case 
self::TYPE_PATTERN:
                
$match = (!empty($this->ipRange) || !empty($this->hostname) || !empty($this->userAgent) || !empty($this->referrer));
                if (!empty(
$this->ipRange)) {
                    
$range = new wfUserIPRange($this->ipRange);
                    
$match $match && $range->isIPInRange($ip);
                }
                if (!empty(
$this->hostname)) {
                    
$hostname wfUtils::reverseLookup($ip);
                    
$match $match && preg_match(wfUtils::patternToRegex($this->hostname), $hostname);
                }
                if (!empty(
$this->userAgent)) {
                    
$match $match && fnmatch($this->userAgent$userAgentFNM_CASEFOLD);
                }
                if (!empty(
$this->referrer)) {
                    
$match $match && fnmatch($this->referrer$referrerFNM_CASEFOLD);
                }
                
                if (
$match) {
                    return 
self::MATCH_PATTERN;
                }
                
                break;
            case 
self::TYPE_COUNTRY:
                if (!
wfConfig::get('isPaid')) {
                    return 
self::MATCH_NONE;
                }
                
                
//Bypass Redirect URL Hit
                
$bareRequestURI wfUtils::extractBareURI($_SERVER['REQUEST_URI']);
                
$bareBypassRedirURI wfUtils::extractBareURI(wfConfig::get('cbl_bypassRedirURL'''));
                if (
$bareBypassRedirURI && $bareRequestURI == $bareBypassRedirURI) {
                    
$bypassRedirDest wfConfig::get('cbl_bypassRedirDest''');
                    if (
$bypassRedirDest) {
                        
wfUtils::setcookie('wfCBLBypass'wfBlock::countryBlockingBypassCookieValue(), time() + (86400 365), '/'nullwfUtils::isFullSSL(), true);
                        return 
self::MATCH_COUNTRY_REDIR_BYPASS;
                    }
                }
                
                
//Bypass View URL Hit
                
$bareBypassViewURI wfUtils::extractBareURI(wfConfig::get('cbl_bypassViewURL'''));
                if (
$bareBypassViewURI && $bareBypassViewURI == $bareRequestURI) {
                    
wfUtils::setcookie('wfCBLBypass'wfBlock::countryBlockingBypassCookieValue(), time() + (86400 365), '/'nullwfUtils::isFullSSL(), true);
                    return 
self::MATCH_NONE;
                }
                
                
//Early exit checks
                
if ($this->_shouldBypassCountryBlocking()) { //Has valid bypass cookie
                    
return self::MATCH_NONE;
                }
                
                if (
$this->blockLogin) {
                    
add_filter('authenticate', array($this'_checkForBlockedCountryFilter'), 11);
                }
                
                if (!
$this->blockLogin && $this->_isAuthRequest()) { //Not blocking login and this is a login request
                    
return self::MATCH_NONE;
                }
                else if (!
$this->blockSite && !$this->_isAuthRequest()) { //Not blocking site and this may be a site request
                    
return self::MATCH_NONE;
                }
                else if (
is_user_logged_in() && !wfConfig::get('cbl_loggedInBlocked'false)) { //Not blocking logged in users and a login session exists
                    
return self::MATCH_NONE;
                }
                
                
//Block everything
                
if ($this->blockSite && $this->blockLogin) {
                    return 
$this->_checkForBlockedCountry();
                }
                
                
//Block the login form itself and any attempt to authenticate
                
if ($this->blockLogin && $this->_isAuthRequest()) {
                    return 
$this->_checkForBlockedCountry();
                }
                
                
//Block requests that aren't to the login page, xmlrpc.php, or a user already logged in
                
if ($this->blockSite && !$this->_isAuthRequest() && !defined('XMLRPC_REQUEST')) {
                    return 
$this->_checkForBlockedCountry();
                }
                
                
//XMLRPC is inaccesible when public portion of the site and auth is disabled
                
if ($this->blockLogin && $this->blockSite && defined('XMLRPC_REQUEST')) {
                    return 
$this->_checkForBlockedCountry();
                }
                
                break;
        }
        
        return 
self::MATCH_NONE;
    }
    
    
/**
     * Returns whether or not the current request should be treated as an auth request.
     * 
     * @return bool
     */
    
private function _isAuthRequest() {
        if ((
strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false)) {
            return 
true;
        }
        return 
false;
    }
    
    
/**
     * Tests whether or not the country blocking bypass cookie is set and valid.
     * 
     * @return bool
     */
    
private function _shouldBypassCountryBlocking() {
        if (isset(
$_COOKIE['wfCBLBypass']) && $_COOKIE['wfCBLBypass'] == wfBlock::countryBlockingBypassCookieValue()) {
            return 
true;
        }
        return 
false;
    }
    
    
/**
     * Checks the country block against the requesting IP, returning the action to take.
     * 
     * @return int
     */
    
private function _checkForBlockedCountry() {
        
$blockedCountries $this->countries;
        
$bareRequestURI untrailingslashit(wfUtils::extractBareURI($_SERVER['REQUEST_URI']));
        
$IP wfUtils::getIP();
        if (
$country wfUtils::IP2Country($IP)) {
            foreach (
$blockedCountries as $blocked) {
                if (
strtoupper($blocked) == strtoupper($country)) { //At this point we know the user has been blocked
                    
if (wfConfig::get('cbl_action') == 'redir') {
                        
$redirURL wfConfig::get('cbl_redirURL');
                        
$eRedirHost wfUtils::extractHostname($redirURL);
                        
$isExternalRedir false;
                        if (
$eRedirHost && $eRedirHost != wfUtils::extractHostname(home_url())) { //It's an external redirect...
                            
$isExternalRedir true;
                        }
                        
                        if ((!
$isExternalRedir) && untrailingslashit(wfUtils::extractBareURI($redirURL)) == $bareRequestURI) { //Is this the URI we want to redirect to, then don't block it
                            
return self::MATCH_NONE;
                        }
                        else {
                            return 
self::MATCH_COUNTRY_REDIR;
                        }
                    }
                    else {
                        return 
self::MATCH_COUNTRY_BLOCK;
                    }
                }
            }
        }
        
        return 
self::MATCH_NONE;
    }
    
    
/**
     * Filter hook for the country blocking check. Does nothing if not blocked, otherwise presents the block page and exits.
     * 
     * Note: Must remain `public` for callback to work.
     */
    
public function _checkForBlockedCountryFilter($user) {
        
$block $this->_checkForBlockedCountry();
        if (
$block == self::MATCH_NONE) { 
            return 
$user;
        }
        
        
$log wfLog::shared();
        
$log->getCurrentRequest()->actionDescription __('blocked access via country blocking''wordfence');
        
wfConfig::inc('totalCountryBlocked');
        
wfActivityReport::logBlockedIP(wfUtils::getIP(), null'country');
        
$log->do503(3600wfI18n::__('Access from your area has been temporarily limited for security reasons''wordfence')); //exits
    
}
    
    
/**
     * Adds $quantity to the blocked count and sets the timestamp for lastAttempt.
     * 
     * @param int $quantity
     * @param bool|int $timestamp
     */
    
public function recordBlock($quantity 1$timestamp false) {
        if (
$timestamp === false) {
            
$timestamp time();
        }
        
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
$wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `blockedHits` = `blockedHits` + %d, `lastAttempt` = GREATEST(`lastAttempt`, %d) WHERE `id` = %d"$quantity$timestamp$this->id));
        
$this->_type false//Trigger a re-fetch next access 
    
}
    
    
/**
     * Returns an array suitable for JSON of the values needed to edit the block.
     * 
     * @return array
     */
    
public function editValues() {
        switch (
$this->type) {
            case 
self::TYPE_COUNTRY:
                return array(
                    
'blockLogin' => wfUtils::truthyToInt($this->blockLogin),
                    
'blockSite' => wfUtils::truthyToInt($this->blockSite),
                    
'countries' => $this->countries,
                    
'reason' => $this->reason,
                    
'expiration' => $this->expiration,
                );
        }
        
        return array();
    }
    
    
/**
     * Removes this block record. May trigger an additional query to fetch the notification data if $notify is true and
     * the record is ID-only.
     * 
     * @param bool $notify If true, will dispatch the `wordfence_deleted_block` action.
     * @return null|wfBlock null if a failure occurs, otherwise the block
     */
    
public function remove($notify false) {
        
$result self::removeMultiple(array($this), $notify);
        if (
is_array($result)) {
            return 
$result[0];
        }
        return 
null;
    }
    
    
/**
     * Deletes the given block, optionally dispatching the `wordfence_deleted_block` action for each block. May trigger 
     * an additional query to fetch the notification data if $notify is true and any record is ID-only.
     * 
     * @param wfBlock[] $blocks
     * @param bool $notify If true, will dispatch the `wordfence_deleted_block` action.
     * @return null|wfBlock[] null if a failure occurs, otherwise the blocks
     */
    
public static function removeMultiple($blocks$notify false) {
        if (empty(
$blocks)) { return array(); }
        
        global 
$wpdb;
        
$blocksTable wfBlock::blocksTable();
        
        
$blockIDs array_map(function($b) { return intval($b->id); }, $blocks);
        
$inClause implode(', '$blockIDs);
        
        if (
$notify) {
            
$blockIDsToPopulate array_filter(array_map(function($b) { return ($b->_type === false intval($b->id) : null); }, $blocks));
            if (!empty(
$blockIDsToPopulate)) {
                
$populateInClause implode(', '$blockIDsToPopulate);
                
$data wfUtils::array_kmap(function($r) { return array($r['id'] => $r); }, $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `id` IN ({$populateInClause})"ARRAY_A));
                foreach (
$blocks as $b) { /** @var wfBlock $b */
                    
if (isset($data[$b->id])) {
                        
$b->_populateData($data[$b->id]);
                    }
                }
            }
        }
        
        
$query "DELETE FROM `{$blocksTable}` WHERE `id` IN (" $inClause ")";
        if (
$wpdb->query($query) !== false) {
            
$payload = array();
            if (
$notify) {
                foreach (
$blocks as $b) {
                    
$type $b->type;
                    
$reason $b->reason;
                    
$parameters = (($type != self::TYPE_COUNTRY && $type != self::TYPE_PATTERN) ? $b->ip $b->parameters);
                    
                    
/**
                     * Fires when a blocking rule is deleted by manual action.
                     *
                     * @since 8.0.0
                     *
                     * @param string $type The type of block.
                     * @param string $reason The reason of the block.
                     * @param array|null $parameters The parameters of the block if needed for disambiguation (e.g., the country block returns null because there is only one rule at most)
                     */
                    
do_action('wordfence_deleted_block'$type$reason$parameters);
                }
            }
            
            return 
$blocks;
        }
        
        return 
null;
    }
}

All system for education purposes only. For more tools: Telegram @jackleet

Mr.X Private Shell

Logo
-
New File | New Folder
Command
SQL