XylotrechusZ
<?php
namespace WPForms\Pro\AntiSpam;
/**
* Country Filter class.
*
* @since 1.7.8
*/
class CountryFilter {
/**
* User IP.
*
* @since 1.7.8
*
* @var string $ip
*/
private $ip;
/**
* A list of known CDN/Proxy headers which contain country code.
*
* @since 1.7.8
*/
const CDN_HEADERS = [
'HTTP_CF_IPCOUNTRY',
'HTTP_X_GEOIP_COUNTRY',
'CloudFront-Viewer-Country-Region',
'X-Country-Code',
];
/**
* Available sources for retrieving country by API.
*
* @since 1.7.8
*
* @var string[]
*/
private $available_geo_ips = [
'wpforms' => 'https://geo.wpforms.com/v3/geolocate/json/%s',
'ipapi' => 'https://ipapi.co/%s/json',
'keycdn' => 'https://tools.keycdn.com/geo.json?host=%s',
];
/**
* Init class.
*
* @since 1.7.8
*/
public function init() {
$this->ip = wpforms_get_ip();
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.8
*/
private function hooks() {
add_filter( 'wpforms_process_initial_errors', [ $this, 'process' ], 10, 2 );
add_filter( 'wpforms_pro_anti_spam_country_filter_request_keycdn_args', [ $this, 'modify_keycdn_request_args' ], 10, 2 );
add_action( 'wpforms_admin_builder_anti_spam_panel_content', [ $this, 'get_settings' ], 10, 1 );
add_filter( 'wpforms_save_form_args', [ $this, 'save' ], 10, 3 );
}
/**
* Add content to the 'Spam Protection and Security' panel in the Form Builder.
*
* @since 1.7.8
*
* @param array $form_data Form data and settings.
*/
public function get_settings( $form_data ) {
ob_start();
wpforms_panel_field(
'toggle',
'anti_spam',
'enable',
$form_data,
__( 'Enable country filter', 'wpforms' ),
[
'parent' => 'settings',
'subsection' => 'country_filter',
'tooltip' => __( 'Allow or deny entries from the countries you select.', 'wpforms' ),
'input_class' => 'wpforms-panel-field-toggle-next-field',
]
);
?>
<div class="wpforms-panel-field wpforms-panel-field-country-filter-body">
<div class="wpforms-panel-field-country-filter-block">
<label><?php esc_html_e( 'Country Filter', 'wpforms' ); ?></label>
<div class="wpforms-panel-field-country-filter-block-row">
<?php
wpforms_panel_field(
'select',
'anti_spam',
'action',
$form_data,
'',
[
'parent' => 'settings',
'subsection' => 'country_filter',
'options' => [
'allow' => __( 'Allow', 'wpforms' ),
'deny' => __( 'Deny', 'wpforms' ),
],
'class' => 'wpforms-panel-field-country-filter-block-row-action',
]
);
?>
<span class="wpforms-panel-field-country-filter-block-row-separator"><?php esc_html_e( 'entries from', 'wpforms' ); ?></span>
<?php
wpforms_panel_field(
'select',
'anti_spam',
'country_codes',
$form_data,
'',
[
'parent' => 'settings',
'subsection' => 'country_filter',
'options' => wpforms_countries(),
'multiple' => true,
'class' => 'wpforms-panel-field-country-filter-block-row-countries',
]
);
?>
</div>
</div>
<input type="hidden" name="settings[anti_spam][country_filter][country_codes]" class="wpforms-panel-field-country-filter-country-codes-json">
<?php
wpforms_panel_field(
'text',
'anti_spam',
'message',
$form_data,
__( 'Country Filter Message', 'wpforms' ),
[
'parent' => 'settings',
'subsection' => 'country_filter',
'default' => $this->get_default_error_message( $form_data ),
'tooltip' => __( 'Displayed if a visitor from a restricted country tries to submit your form.', 'wpforms' ),
'class' => Helpers::get_filtering_message_classes( $form_data ),
]
);
?>
</div>
<?php
wpforms_panel_fields_group(
ob_get_clean(),
[
'description' => __( 'Restrict form entries based on customizable filters or conditions.', 'wpforms' ),
'title' => __( 'Filtering', 'wpforms' ),
'borders' => [ 'top' ],
]
);
}
/**
* Do not show the form for users from countries covered by the filter.
*
* @since 1.7.8
*
* @param array $errors Form submit errors.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function process( $errors, $form_data ) {
// If the form is set to store spam entries detected by filtering, we do not need to display the error message.
if ( Helpers::is_store_filtering_spam( $form_data ) ) {
return $errors;
}
if ( ! $this->is_valid( $form_data ) ) {
$form_id = ! empty( $form_data['id'] ) ? $form_data['id'] : 0;
$errors[ $form_id ]['footer'] = $this->get_error_message( $form_data );
}
return $errors;
}
/**
* Check if form submission is valid.
*
* @since 1.9.2
*
* @param array $form_data Form data and settings.
*
* @uses is_allow_submission, is_deny_submission
*
* @return bool
*/
public function is_valid( array $form_data ): bool {
if (
! $this->is_enabled( $form_data ) ||
$this->is_local() ||
empty( $this->get_selected_countries( $form_data ) )
) {
return true;
}
$action = $this->get_selected_action( $form_data );
$method = "is_{$action}_submission";
if ( $this->$method( $form_data ) ) {
return true;
}
return false;
}
/**
* Process condition for allow action.
*
* @since 1.7.8
*
* @param array $form_data Form data and settings.
*
* @return bool
*/
private function is_allow_submission( $form_data ) {
return in_array( $this->get_country_code(), $this->get_selected_countries( $form_data ), true );
}
/**
* Process condition for deny action.
*
* @since 1.7.8
*
* @param array $form_data Form data and settings.
*
* @return bool
*/
private function is_deny_submission( $form_data ) {
return ! in_array( $this->get_country_code(), $this->get_selected_countries( $form_data ), true );
}
/**
* Get selected action.
*
* @since 1.7.8
*
* @param array $form_data Form data and settings.
*
* @return string
*/
private function get_selected_action( $form_data ) {
return ! empty( $form_data['settings']['anti_spam']['country_filter']['action'] ) && $form_data['settings']['anti_spam']['country_filter']['action'] === 'deny' ? 'deny' : 'allow';
}
/**
* Get selected country codes.
*
* @since 1.7.8
*
* @param array $form_data Form data and settings.
*
* @return array
*/
private function get_selected_countries( $form_data ) {
return ! empty( $form_data['settings']['anti_spam']['country_filter']['country_codes'] ) && is_array( $form_data['settings']['anti_spam']['country_filter']['country_codes'] ) ?
$form_data['settings']['anti_spam']['country_filter']['country_codes'] :
[];
}
/**
* Get CDN/Proxy country related headers from $_SERVER array.
*
* @since 1.7.8
*
* @return string
*/
private function get_country_from_cdn_headers() {
/**
* Filter CDN/Proxy headers with country code.
*
* @since 1.7.8
*
* @param array $cdn_headers Array of available country code CDN/Proxy headers.
*/
$headers = apply_filters( 'wpforms_pro_anti_spam_country_filter_get_country_from_cdn_headers', self::CDN_HEADERS );
foreach ( $headers as $header ) {
if ( ! empty( $_SERVER[ $header ] ) ) {
return strtoupper( sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) );
}
}
return '';
}
/**
* Get user country code from 3rd party API by IP.
*
* @since 1.7.8
*
* @return string
*/
private function get_country_from_api() {
foreach ( array_keys( $this->available_geo_ips ) as $source ) {
if ( ! is_string( $source ) ) {
continue;
}
if ( ! empty( $this->available_geo_ips[ $source ] ) ) {
$country_code = $this->request( $source, $this->available_geo_ips[ $source ], $this->ip );
}
if ( ! empty( $country_code ) ) {
return $country_code;
}
}
return '';
}
/**
* Get user country code from the fastest available source.
*
* @since 1.7.8
*
* @return string
*/
private function get_country_code() {
/**
* Allow to modify user's country code before our logic.
*
* @since 1.7.8
*
* @param string $code Country code.
*
* @return string
*/
$code = apply_filters( 'wpforms_pro_anti_spam_country_filter_get_country_code_before_processing', '' );
if ( ! empty( $code ) ) {
return $code;
}
$country_code = $this->get_country_from_cdn_headers();
if ( empty( $country_code ) ) {
$country_code = $this->get_country_from_api();
}
/**
* Allow to modify user's country code after our logic.
*
* @since 1.7.8
*
* @param string $code Country code.
*
* @return string
*/
return apply_filters( 'wpforms_pro_anti_spam_country_filter_get_country_code_after_processing', $country_code );
}
/**
* Is local IP Address.
*
* @since 1.7.8
*
* @return bool
*/
private function is_local() {
return empty( $this->ip ) || in_array( $this->ip, [ '127.0.0.1', '::1' ], true );
}
/**
* Make request for getting country code from 3rd party API.
*
* @since 1.7.8
*
* @param string $source Source name.
* @param string $endpoint Endpoint.
* @param string $ip IP address.
*
* @uses wpforms_response, ipapi_response, keycdn_response
*
* @return string
*/
private function request( $source, $endpoint, $ip ) {
$endpoint = sprintf( $endpoint, $ip );
/**
* Allow modifying request arguments.
*
* @since 1.7.8
*
* @param array $args Request arguments.
* @param string $ip IP address.
*
* @return array
*/
$args = apply_filters( 'wpforms_pro_anti_spam_country_filter_request_args', [], $ip );
/**
* Allow modifying request arguments for each CDN header.
*
* @since 1.7.8
*
* @param array $args Request arguments.
* @param string $ip IP address.
*
* @return array
*/
$args = (array) apply_filters( "wpforms_pro_anti_spam_country_filter_request_{$source}_args", $args, $ip );
$request = wp_remote_get( $endpoint, $args );
if ( is_wp_error( $request ) ) {
return '';
}
$request = json_decode( wp_remote_retrieve_body( $request ), true );
$method = $source . '_response';
$country_code = $this->{$method}( $request );
return sanitize_text_field( wp_unslash( $country_code ) );
}
/**
* Processing request from WPForms.
*
* @since 1.7.8
*
* @param array $request_body Request body.
*
* @return string
*/
private function wpforms_response( $request_body ) {
return ! empty( $request_body['country_iso'] ) ? $request_body['country_iso'] : '';
}
/**
* Processing request from IpAPI.
*
* @since 1.7.8
*
* @param array $request_body Request body.
*
* @return string
*/
private function ipapi_response( $request_body ) {
return ! empty( $request_body['country'] ) ? $request_body['country'] : '';
}
/**
* Processing request from KeyCDN.
*
* @since 1.7.8
*
* @param array $request_body Request body.
*
* @return string
*/
private function keycdn_response( $request_body ) {
return ! empty( $request_body['data']['geo']['country_code'] ) ? $request_body['data']['geo']['country_code'] : '';
}
/**
* Is Country filter enabled in settings?
*
* @since 1.7.8
*
* @param array $form_data Form data and settings.
*
* @return bool
*/
private function is_enabled( $form_data ) {
return ! empty( $form_data['settings']['anti_spam']['country_filter']['enable'] );
}
/**
* Get error message.
*
* @since 1.7.8
* @since 1.9.2 Changed method visibility.
*
* @param array $form_data Form data and settings.
*
* @return string
*/
public function get_error_message( $form_data ) {
return ! empty( $form_data['settings']['anti_spam']['country_filter']['message'] ) ?
$form_data['settings']['anti_spam']['country_filter']['message'] :
$this->get_default_error_message( $form_data );
}
/**
* Return default error message.
*
* @since 1.7.8
*
* @param array $form_data Form Data.
*
* @return string
*/
private function get_default_error_message( $form_data ) {
/**
* Modify default error message.
*
* @since 1.7.8
*
* @param string $message Request arguments.
* @param array $form_data Form Data.
*
* @return string
*/
return apply_filters( 'wpforms_pro_anti_spam_country_filter_get_default_error_message', esc_html__( 'Sorry, this form does not accept submissions from your country.', 'wpforms' ), $form_data );
}
/**
* Modify request arguments for the KeyCDN geolocation provider.
*
* @since 1.7.8
*
* @param array $args Request arguments.
* @param string $ip IP address.
*
* @return array
*/
public function modify_keycdn_request_args( $args, $ip ) {
$args['user-agent'] = sprintf( 'keycdn-tools:%s', site_url() );
return $args;
}
/**
* Save country settings.
*
* @since 1.7.8
*
* @param array $form Form array which is usable with `wp_update_post()`.
* @param array $data Data retrieved from $_POST and processed.
* @param array $args Empty by default, may contain custom data not intended to be saved, but used for processing.
*
* @return array
*/
public function save( $form, $data, $args ) {
if ( empty( $args['context'] ) || $args['context'] !== 'save_form' ) {
return $form;
}
// Get a filtered form content.
$form_data = json_decode( stripslashes( $form['post_content'] ), true );
$country_codes = isset( $data['settings']['anti_spam']['country_filter'] ) ?
json_decode( $data['settings']['anti_spam']['country_filter']['country_codes'], true ) :
[];
if ( ! is_array( $country_codes ) ) {
$country_codes = [];
}
$form_data['settings']['anti_spam']['country_filter']['country_codes'] = array_filter(
array_map(
static function ( $country_code ) {
$country_code = strtoupper( $country_code );
return strlen( $country_code ) === 2 ? $country_code : '';
},
$country_codes
)
);
$form['post_content'] = wpforms_encode( $form_data );
return $form;
}
}