diff --git a/Lib/WeDevs_Settings_API.php b/Lib/WeDevs_Settings_API.php index 7a5cd9ad5..9f3098aa8 100644 --- a/Lib/WeDevs_Settings_API.php +++ b/Lib/WeDevs_Settings_API.php @@ -140,6 +140,7 @@ function admin_init() { 'max' => isset( $option['max'] ) ? $option['max'] : '', 'step' => isset( $option['step'] ) ? $option['step'] : '', 'is_pro_preview' => ! empty( $option['is_pro_preview'] ) ? $option['is_pro_preview'] : false, + 'depends_on' => ! empty( $option['depends_on'] ) ? $option['depends_on'] : '', ); add_settings_field( $section . '[' . $option['name'] . ']', $option['label'], (isset($option['callback']) ? $option['callback'] : array($this, 'callback_' . $type )), $section, $section, $args ); @@ -173,14 +174,17 @@ public function get_field_description( $args ) { * @param array $args settings field args */ function callback_text( $args ) { - $value = esc_attr( $this->get_option( $args['id'], $args['section'], $args['std'] ) ); $size = isset( $args['size'] ) && !is_null( $args['size'] ) ? $args['size'] : 'regular'; $type = isset( $args['type'] ) ? $args['type'] : 'text'; $placeholder = empty( $args['placeholder'] ) ? '' : ' placeholder="' . $args['placeholder'] . '"'; $disabled = ! empty( $args['is_pro_preview'] ) && $args['is_pro_preview'] ? 'disabled' : ''; + $depends_on = ! empty( $args['depends_on'] ) ? $args['depends_on'] : ''; - $html = sprintf( '', $type, $size, $args['section'], $args['id'], $value, $placeholder, $disabled ); + $html = sprintf( + '', + $type, $size, $args['section'], $args['id'], $value, $placeholder, $disabled, $depends_on + ); $html .= $this->get_field_description( $args ); if ( ! empty( $args['is_pro_preview'] ) && $args['is_pro_preview'] ) { @@ -460,6 +464,38 @@ function callback_color( $args ) { echo $html; } + /** + * Displays a toggle field for a settings field + * + * @param array $args settings field args + */ + public function callback_toggle( $args ) { + $value = esc_attr( $this->get_option( $args['id'], $args['section'], $args['std'] ) ); + $disabled = ! empty( $args['is_pro_preview'] ) && $args['is_pro_preview'] ? 'disabled' : ''; + $name = $args['section'] . '[' . $args['id'] . ']'; + ?> +
+ +
+ + + diff --git a/admin/form-builder/assets/js/components/form-cloudflare_turnstile/index.js b/admin/form-builder/assets/js/components/form-cloudflare_turnstile/index.js new file mode 100644 index 000000000..1de5a8a4b --- /dev/null +++ b/admin/form-builder/assets/js/components/form-cloudflare_turnstile/index.js @@ -0,0 +1,36 @@ +/** + * Field template: Cloudflare Turnstile + */ +Vue.component('form-cloudflare_turnstile', { + template: '#tmpl-wpuf-form-cloudflare_turnstile', + + mixins: [ + wpuf_mixins.form_field_mixin + ], + + computed: { + has_turnstile_api_keys: function () { + return wpuf_form_builder.turnstile_site && wpuf_form_builder.turnstile_secret; + }, + + no_api_keys_msg: function () { + return wpuf_form_builder.field_settings.turnstile.validator.msg; + }, + + turnstile_image: function () { + var base_url = wpuf_form_builder.asset_url + '/images/cloudflare-placeholder-'; + + if (this.field.turnstile_theme === 'dark') { + base_url += 'dark'; + } else { + base_url += 'light'; + } + + if (this.field.turnstile_size === 'compact') { + base_url += '-compact'; + } + + return base_url + '.png'; + } + } +}); diff --git a/admin/form-builder/assets/js/components/form-cloudflare_turnstile/template.php b/admin/form-builder/assets/js/components/form-cloudflare_turnstile/template.php new file mode 100644 index 000000000..6eb749161 --- /dev/null +++ b/admin/form-builder/assets/js/components/form-cloudflare_turnstile/template.php @@ -0,0 +1,12 @@ +
+ + + +
diff --git a/admin/form-builder/assets/js/mixins/global.js b/admin/form-builder/assets/js/mixins/global.js index 60ecffd03..0482a6834 100644 --- a/admin/form-builder/assets/js/mixins/global.js +++ b/admin/form-builder/assets/js/mixins/global.js @@ -44,6 +44,10 @@ Vue.mixin({ return (wpuf_form_builder.recaptcha_site && wpuf_form_builder.recaptcha_secret) ? true : false; }, + has_turnstile_api_keys: function () { + return wpuf_form_builder.turnstile_site && wpuf_form_builder.turnstile_secret; + }, + containsField: function(field_name) { var self = this, i = 0; diff --git a/assets/css/admin.css b/assets/css/admin.css index bfd70f2d3..27f72c7db 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -944,6 +944,50 @@ span.pro-icon:hover .wpuf-pro-field-tooltip { .wrap h2.with-headway-icon #HW_frame_cont { top: 78px !important; } +.wpuf-toggle-switch { + position: relative; + display: inline-block; + width: 50px; + height: 26px; +} +.wpuf-toggle-switch input { + display: none; +} +.wpuf-toggle-switch input:checked + .slider { + background-color: #0073aa; +} +.wpuf-toggle-switch input:focus + .slider { + box-shadow: 0 0 1px #2196F3; +} +.wpuf-toggle-switch input:checked + .slider:before { + transform: translateX(26px); +} +.wpuf-toggle-switch .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .2s; +} +.wpuf-toggle-switch .slider.round { + border-radius: 34px; +} +.wpuf-toggle-switch .slider.round:before { + border-radius: 50%; +} +.wpuf-toggle-switch .slider::before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 4px; + background-color: white; + transition: .2s; +} @media only screen and (max-width: 1399px) { a.wpuf-button.button-upgrade-to-pro { padding: 10px; diff --git a/assets/css/admin/wpuf-module.css b/assets/css/admin/wpuf-module.css index 95c0f032f..3485758af 100644 --- a/assets/css/admin/wpuf-module.css +++ b/assets/css/admin/wpuf-module.css @@ -35,65 +35,6 @@ line-height: 1.6em; } -.wpuf-toggle-switch { - position: relative; - display: inline-block; - width: 50px; - height: 26px; -} - -.wpuf-toggle-switch input { - display: none; -} - -.wpuf-toggle-switch .slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #ccc; - -webkit-transition: .4s; - transition: .4s; -} - -.wpuf-toggle-switch .slider:before { - position: absolute; - content: ""; - height: 18px; - width: 18px; - left: 3px; - bottom: 4px; - background-color: white; - -webkit-transition: .4s; - transition: .4s; -} - -.wpuf-toggle-switch input:checked + .slider { - background-color: #0073aa; -} - -.wpuf-toggle-switch input:focus + .slider { - -webkit-box-shadow: 0 0 1px #2196F3; - box-shadow: 0 0 1px #2196F3; -} - -.wpuf-toggle-switch input:checked + .slider:before { - -webkit-transform: translateX(26px); - -ms-transform: translateX(26px); - transform: translateX(26px); -} - -/* Rounded sliders */ -.slider.round { - border-radius: 34px; -} - -.slider.round:before { - border-radius: 50%; -} - .wpuf-modules .plugin-card { border: 1px solid #e5e5e5; -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); diff --git a/assets/css/frontend-forms.css b/assets/css/frontend-forms.css index 0ff853676..39fb30fe9 100644 --- a/assets/css/frontend-forms.css +++ b/assets/css/frontend-forms.css @@ -84,7 +84,7 @@ body .wpuf-error { background-color: #f2dede; color: #a94442; border: 1px solid #ebccd1; - margin: 10px 10px 20px; + margin: 10px 0 20px 0; padding: 10px; -webkit-border-radius: 3px; -moz-border-radius: 3px; diff --git a/assets/images/cloudflare-placeholder-dark-compact.png b/assets/images/cloudflare-placeholder-dark-compact.png new file mode 100644 index 000000000..ed8eae746 Binary files /dev/null and b/assets/images/cloudflare-placeholder-dark-compact.png differ diff --git a/assets/images/cloudflare-placeholder-dark.png b/assets/images/cloudflare-placeholder-dark.png new file mode 100644 index 000000000..417250f0e Binary files /dev/null and b/assets/images/cloudflare-placeholder-dark.png differ diff --git a/assets/images/cloudflare-placeholder-light-compact.png b/assets/images/cloudflare-placeholder-light-compact.png new file mode 100644 index 000000000..be7012d1c Binary files /dev/null and b/assets/images/cloudflare-placeholder-light-compact.png differ diff --git a/assets/images/cloudflare-placeholder-light.png b/assets/images/cloudflare-placeholder-light.png new file mode 100644 index 000000000..b63cd087c Binary files /dev/null and b/assets/images/cloudflare-placeholder-light.png differ diff --git a/assets/js-templates/form-components.php b/assets/js-templates/form-components.php index ca68db439..93414ac83 100644 --- a/assets/js-templates/form-components.php +++ b/assets/js-templates/form-components.php @@ -438,6 +438,21 @@ class="option-chooser-radio" + + + + 'has_turnstile_api_keys', + 'button_class' => 'button-faded', + 'msg_title' => __( 'Site key and Secret key', 'wp-user-frontend' ), + 'msg' => sprintf( + // translators: %s: settings url + __( 'You need to set Site key and Secret key in Settings in order to use "Cloudflare Turnstile" field. Click here to get the these key.', 'wp-user-frontend' ), + admin_url( 'admin.php?page=wpuf-settings' ), + 'https://developers.cloudflare.com/turnstile/' + ), + ]; + } + + /** + * Get field options setting + * + * @since WPUF_SINCE + * + * @return array + */ + public function get_options_settings() { + $settings = [ + [ + 'name' => 'label', + 'title' => __( 'Title', 'wp-user-frontend' ), + 'type' => 'text', + 'section' => 'basic', + 'priority' => 10, + 'help_text' => __( 'Title of the section', 'wp-user-frontend' ), + ], + [ + 'name' => 'turnstile_theme', + 'title' => 'Turnstile Theme', + 'type' => 'radio', + 'options' => [ + 'light' => __( 'Light', 'wp-user-frontend' ), + 'dark' => __( 'Dark', 'wp-user-frontend' ), + ], + 'default' => 'light', + 'section' => 'basic', + 'priority' => 12, + 'help_text' => __( 'Select turnstile theme', 'wp-user-frontend' ), + ], + [ + 'name' => 'turnstile_size', + 'title' => 'Turnstile Size', + 'type' => 'radio', + 'options' => [ + 'normal' => __( 'Normal [Width: 300px, Height: 65px]', 'wp-user-frontend' ), + 'flexible' => __( 'Flexible [Width: 100% (min: 300px), Height: 65px]', 'wp-user-frontend' ), + 'compact' => __( 'Compact [Width: 150px, Height: 140px]', 'wp-user-frontend' ), + ], + 'default' => 'normal', + 'section' => 'basic', + 'priority' => 13, + 'help_text' => __( 'Select turnstile size', 'wp-user-frontend' ), + ], + [ + 'name' => 'turnstile_type', + 'title' => 'Turnstile type', + 'type' => 'radio', + 'options' => [ + 'managed' => __( 'Managed (recommended)', 'wp-user-frontend' ), + 'non_interactive' => __( 'Non-Interactive', 'wp-user-frontend' ), + 'invisible' => __( 'Invisible', 'wp-user-frontend' ), + ], + 'default' => 'managed', + 'section' => 'advanced', + 'priority' => 11, + 'help_text' => __( 'Select turnstile type', 'wp-user-frontend' ), + ], + ]; + + return $settings; + } + + /** + * Get the field props + * + * @since WPUF_SINCE + * + * @return array + */ + public function get_field_props() { + + $props = [ + 'input_type' => 'turnstile', + 'template' => $this->get_type(), + 'label' => '', + 'turnstile_type' => 'managed', + 'turnstile_theme' => 'light', + 'turnstile_size' => 'normal', + 'is_meta' => 'yes', + 'id' => 0, + 'wpuf_cond' => null, + ]; + + return $props; + } +} diff --git a/includes/Free/Simple_Login.php b/includes/Free/Simple_Login.php index 200e7f638..2afac5da4 100644 --- a/includes/Free/Simple_Login.php +++ b/includes/Free/Simple_Login.php @@ -19,6 +19,13 @@ class Simple_Login { private $messages = []; + /** + * Cloudflare Turnstile messages + * + * @var array + */ + private $cf_messages = []; + private static $_instance; public function __construct() { @@ -339,7 +346,7 @@ public function login_form() { $reset = isset( $getdata['reset'] ) ? sanitize_text_field( $getdata['reset'] ) : ''; if ( false === $login_page ) { - return; + return ''; } ob_start(); @@ -394,6 +401,11 @@ public function login_form() { default: $loggedout = isset( $getdata['loggedout'] ) ? sanitize_text_field( $getdata['loggedout'] ) : ''; + $enable_turnstile = wpuf_get_option( 'enable_turnstile', 'wpuf_general', 'off' ); + + if ( 'on' === $enable_turnstile ) { + wp_enqueue_script( 'wpuf-turnstile' ); + } if ( $loggedout === 'true' ) { $this->messages[] = __( 'You are now logged out.', 'wp-user-frontend' ); @@ -410,6 +422,52 @@ public function login_form() { return ob_get_clean(); } + /** + * Verify if cloudflare turnstile request is successful + * + * @since WPUF_SINCE + * + * @return bool + */ + private function verify_cloudflare_turnstile_on_login() { + $nonce = isset( $_POST['wpuf-login-nonce'] ) ? sanitize_key( wp_unslash( $_POST['wpuf-login-nonce'] ) ) : ''; + + if ( isset( $nonce ) && ! wp_verify_nonce( $nonce, 'wpuf_login_action' ) ) { + return false; + } + + $secret = wpuf_get_option( 'turnstile_secret_key', 'wpuf_general', '' ); + + if ( empty( $secret ) ) { + return false; + } + + $remote_addr = ! empty( $_SERVER['REMOTE_ADDR'] ) ? sanitize_url( + wp_unslash( $_SERVER['REMOTE_ADDR'] ) + ) : ''; + + $cf_url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; + $token = ! empty( $_POST['cf-turnstile-response'] ) ? sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) ) : ''; + + // Request data + $data = [ + 'secret' => $secret, + 'response' => $token, + 'remoteip' => $remote_addr, + ]; + + $response = wp_remote_post( $cf_url, [ 'body' => $data ] ); + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( ! empty( $body['success'] ) ) { + return true; + } else { + $this->cf_messages[] = ! empty( $body['error-codes'] ) ? $body['error-codes'] : ''; + + return false; + } + } + /** * Remove selected cookie to have consistency with the login nonce. * fixes WooCommerce Stripe Gateway plugin conflict @@ -447,10 +505,24 @@ public function process_login() { return; } - $log = isset( $_POST['log'] ) ? esc_attr( wp_unslash( $_POST['log'] ) ) : ''; - $pwd = isset( $_POST['pwd'] ) ? trim( $_POST['pwd'] ) : ''; + $log = isset( $_POST['log'] ) ? sanitize_text_field( wp_unslash( $_POST['log'] ) ) : ''; + $pwd = isset( $_POST['pwd'] ) ? sanitize_text_field( ( wp_unslash( $_POST['pwd'] ) ) ) : ''; // $g_recaptcha_response = isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : ''; + if ( ! $this->verify_cloudflare_turnstile_on_login() ) { + $errors = ! empty( $this->cf_messages[0] ) ? $this->cf_messages[0] : ''; + $errors = implode( ', ', $errors ); + $this->login_errors[] = + sprintf( + // translators: %1$s and %2$s are strong tags, %3$s is the error message + __( '%1$sError%2$s: Cloudflare Turnstile verification failed. Reasons: [%3$s]', 'wp-user-frontend' ), + '', + '', + $errors + ); + '' . __( 'Error', 'wp-user-frontend' ) . ': ' . __( 'Cloudflare Turnstile verification failed. Reasons: [', 'wp-user-frontend' ); + } + $validation_error = new WP_Error(); $validation_error = apply_filters( 'wpuf_process_login_errors', $validation_error, $log, $pwd ); diff --git a/includes/functions/settings-options.php b/includes/functions/settings-options.php index 16d75bb82..315862b35 100644 --- a/includes/functions/settings-options.php +++ b/includes/functions/settings-options.php @@ -159,6 +159,29 @@ function wpuf_settings_fields() { 'desc' => __( 'Register here to get reCaptcha Site and Secret keys.', 'wp-user-frontend' ), ], + [ + 'name' => 'enable_turnstile', + 'label' => __( 'Enable Turnstile', 'wp-user-frontend' ), + 'type' => 'toggle', + 'default' => 'off', + ], + [ + 'name' => 'turnstile_site_key', + 'label' => __( 'Turnstile Site Key', 'wp-user-frontend' ), + 'depends_on' => 'enable_turnstile', + ], + [ + 'name' => 'turnstile_secret_key', + 'label' => __( 'Turnstile Secret Key', 'wp-user-frontend' ), + 'depends_on' => 'enable_turnstile', + 'desc' => sprintf( + // translators: %s is a link + __( + 'Register here to get Turnstile Site and Secret keys.', + 'wp-user-frontend' + ), esc_url( 'https://developers.cloudflare.com/turnstile/' ) + ), + ], [ 'name' => 'custom_css', 'label' => __( 'Custom CSS codes', 'wp-user-frontend' ), @@ -431,6 +454,17 @@ function wpuf_settings_fields() { 'type' => 'checkbox', 'default' => 'off', ], + [ + 'name' => 'login_form_turnstile', + 'label' => __( 'Turnstile in Login Form', 'wp-user-frontend' ), + 'desc' => __( + 'If enabled, users have to verify Cloudflare Turnstile in login page. Also, make sure that Turnstile is configured properly from General Options', + 'wp-user-frontend' + ), + 'type' => 'toggle', + 'default' => 'off', + 'depends_on' => 'enable_turnstile', + ], ] ), 'wpuf_payment' => apply_filters( 'wpuf_options_payment', [ [ diff --git a/templates/login-form.php b/templates/login-form.php index c23aaacb1..2b12921ea 100644 --- a/templates/login-form.php +++ b/templates/login-form.php @@ -30,8 +30,13 @@

- - + +

+ +
+ + +