PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.28
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.28
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / models / fields / FrmFieldCaptcha.php

FrmFieldCaptcha.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.28, at classes/models/fields/FrmFieldCaptcha.php

448 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 /**
7 * @since 3.0
8 */
9 class FrmFieldCaptcha extends FrmFieldType {
10
11 /**
12 * @var string
13 *
14 * @since 3.0
15 */
16 protected $type = 'captcha';
17
18 /**
19 * @return string
20 */
21 protected function include_form_builder_file() {
22 return FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/field-captcha.php';
23 }
24
25 /**
26 * Returns the image name for a captcha.
27 *
28 * @return string
29 */
30 public static function get_captcha_image_name() {
31 $frm_settings = FrmAppHelper::get_settings();
32 $active_captcha = $frm_settings->active_captcha;
33
34 return $active_captcha === 'recaptcha' && $frm_settings->re_type === 'v3' ? 'recaptcha_v3' : $active_captcha;
35 }
36
37 /**
38 * @return array
39 */
40 protected function field_settings_for_type() {
41 $settings = FrmCaptchaFactory::get_settings_object();
42 return array(
43 'required' => false,
44 'invalid' => true,
45 'captcha_size' => $settings->should_show_captcha_size(),
46 'captcha_theme' => $settings->should_show_captcha_theme(),
47 'captcha_theme_auto_option' => $settings->should_show_captcha_theme_auto_option(),
48 'default' => false,
49 );
50 }
51
52 /**
53 * @return array
54 */
55 protected function new_field_settings() {
56 $frm_settings = FrmAppHelper::get_settings();
57
58 return array(
59 'invalid' => $frm_settings->re_msg,
60 );
61 }
62
63 /**
64 * @return array
65 */
66 protected function extra_field_opts() {
67 return array(
68 'label' => 'none',
69 'captcha_size' => 'normal',
70 'captcha_theme' => 'light',
71 );
72 }
73
74 /**
75 * Replace the "for" attribute for captcha field so it matches the response ID.
76 *
77 * @param array $args
78 * @param string $html
79 *
80 * @return string
81 */
82 protected function before_replace_html_shortcodes( $args, $html ) {
83 $settings = FrmCaptchaFactory::get_settings_object();
84 return str_replace( ' for="field_[key]"', ' for="' . esc_attr( $settings->token_field ) . '"', $html );
85 }
86
87 /**
88 * @param array $args
89 * @param array $shortcode_atts
90 *
91 * @return string
92 */
93 public function front_field_input( $args, $shortcode_atts ) {
94 if ( ! self::should_show_captcha() ) {
95 return '';
96 }
97
98 $frm_settings = FrmAppHelper::get_settings();
99 $settings = FrmCaptchaFactory::get_settings_object();
100 $div_attributes = array(
101 'id' => $args['html_id'],
102 'class' => $this->class_prefix( $frm_settings ) . $this->captcha_class( $frm_settings ),
103 'data-sitekey' => $settings->get_pubkey(),
104 );
105
106 if ( 'turnstile' === $frm_settings->active_captcha ) {
107 $captcha_language = $this->get_captcha_language();
108
109 if ( $captcha_language ) {
110 $div_attributes['data-language'] = $captcha_language;
111 }
112 }
113
114 $div_attributes = $settings->add_front_end_element_attributes( $div_attributes, $this->field );
115
116 return '<div ' . FrmAppHelper::array_to_html_params( $div_attributes ) . '></div>';
117 }
118
119 /**
120 * @since 6.25
121 *
122 * @return string
123 */
124 private function get_captcha_language() {
125 /**
126 * Allows updating the captcha language.
127 *
128 * @since 6.25
129 *
130 * @param string $lang
131 * @param array $field
132 */
133 return apply_filters( 'frm_captcha_lang', get_bloginfo( 'language' ), $this->field );
134 }
135
136 /**
137 * Load the captcha script.
138 *
139 * @param array $args
140 *
141 * @return void
142 */
143 protected function load_field_scripts( $args ) {
144 $api_js_url = $this->api_url();
145
146 wp_register_script( 'captcha-api', $api_js_url, array( 'formidable' ), '3', true );
147 wp_enqueue_script( 'captcha-api' );
148 }
149
150 /**
151 * Get the URL for the script JS that is loaded on the front end.
152 *
153 * @return string
154 */
155 protected function api_url() {
156 $frm_settings = FrmAppHelper::get_settings();
157 $active_mode = $frm_settings->active_captcha;
158
159 if ( 'recaptcha' === $active_mode ) {
160 return $this->recaptcha_api_url( $frm_settings );
161 }
162
163 if ( 'hcaptcha' === $active_mode ) {
164 return $this->hcaptcha_api_url();
165 }
166
167 return $this->turnstile_api_url();
168 }
169
170 /**
171 * @param FrmSettings $frm_settings
172 *
173 * @return string
174 */
175 protected function recaptcha_api_url( $frm_settings ) {
176 $api_js_url = 'https://www.google.com/recaptcha/api.js?';
177
178 if ( $this->allow_multiple( $frm_settings ) ) {
179 $api_js_url .= '&onload=frmRecaptcha&render=explicit';
180 }
181
182 $lang = apply_filters( 'frm_recaptcha_lang', $frm_settings->re_lang, $this->field );
183
184 if ( $lang ) {
185 $api_js_url .= '&hl=' . $lang;
186 }
187
188 // Since this URL initially ends with ? and we never use add_query_arg, remove the extra
189 // & that appears immediately after the ?
190 $api_js_url = str_replace( '?&', '?', $api_js_url );
191
192 /**
193 * @param string $api_js_url
194 */
195 return apply_filters( 'frm_recaptcha_js_url', $api_js_url );
196 }
197
198 /**
199 * @since 6.0
200 *
201 * @return string
202 */
203 protected function hcaptcha_api_url() {
204 $api_js_url = 'https://js.hcaptcha.com/1/api.js';
205 $lang = $this->get_captcha_language();
206
207 if ( $lang ) {
208 // Language might be in the format of en-US, fr-FR, etc. In that case, we need to extract the first part to comply with the hcaptcha api request format.
209 $lang_parts = explode( '-', $lang );
210 $api_js_url .= '?hl=' . $lang_parts[0];
211 }
212
213 $api_js_url = add_query_arg( 'onload', 'frmHcaptcha', $api_js_url );
214
215 /**
216 * Allows updating hcaptcha js api url.
217 *
218 * @since 6.0
219 *
220 * @param string $api_js_url
221 */
222 return apply_filters( 'frm_hcaptcha_js_url', $api_js_url );
223 }
224
225 /**
226 * @since 6.8.4
227 *
228 * @return string
229 */
230 protected function turnstile_api_url() {
231 $api_js_url = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=frmTurnstile&render=explicit';
232
233 /**
234 * Allows updating hcaptcha js api url.
235 *
236 * @since 6.8.4
237 *
238 * @param string $api_js_url
239 */
240 $api_js_url = apply_filters( 'frm_turnstile_js_url', $api_js_url );
241
242 // Prevent render=explicit from happening twice in case someone patched
243 // The double rendering issue using the frm_turnstile_js_url hook.
244 return str_replace(
245 '&render=explicit&render=explicit',
246 '&render=explicit',
247 $api_js_url
248 );
249 }
250
251 /**
252 * @param FrmSettings $frm_settings
253 *
254 * @return string
255 *
256 * @psalm-return ''|'frm-'
257 */
258 protected function class_prefix( $frm_settings ) {
259 return FrmCaptchaFactory::get_settings_object()->get_class_prefix( $this->allow_multiple( $frm_settings ) );
260 }
261
262 /**
263 * @param FrmSettings $frm_settings This isn't used anymore. It's only there for backwards compatibility.
264 *
265 * @return string
266 *
267 * @psalm-return 'g-recaptcha'|'h-captcha'
268 */
269 protected function captcha_class( $frm_settings ) {
270 $settings = FrmCaptchaFactory::get_settings_object();
271 return $settings->get_element_class_name();
272 }
273
274 /**
275 * @param FrmSettings $frm_settings
276 *
277 * @return bool
278 */
279 protected function allow_multiple( $frm_settings ) {
280 return $frm_settings->re_multi;
281 }
282
283 /**
284 * @since 4.07
285 *
286 * @param array $args
287 *
288 * @return array
289 */
290 protected function validate_against_api( $args ) {
291 $errors = array();
292 $frm_settings = FrmAppHelper::get_settings();
293 $resp = $this->send_api_check();
294 $response = json_decode( wp_remote_retrieve_body( $resp ), true );
295
296 if ( is_wp_error( $resp ) ) {
297 $error_string = $resp->get_error_message();
298 $errors[ 'field' . $args['id'] ] = __( 'There was a problem verifying your captcha', 'formidable' );
299 $errors[ 'field' . $args['id'] ] .= ' ' . $error_string;
300 return $errors;
301 }
302
303 if ( ! is_array( $response ) ) {
304 return $errors;
305 }
306
307 if ( $frm_settings->active_captcha === 'recaptcha' ) {
308 if ( 'v3' === $frm_settings->re_type && array_key_exists( 'score', $response ) ) {
309 $threshold = floatval( $frm_settings->re_threshold );
310 $score = floatval( $response['score'] );
311
312 $this->set_score( $score );
313
314 if ( $score < $threshold ) {
315 $response['success'] = false;
316 }
317 }
318 }
319
320 if ( ! isset( $response['success'] ) || $response['success'] ) {
321 return $errors;
322 }
323
324 // What happens when the CAPTCHA was entered incorrectly
325 $invalid_message = FrmField::get_option( $this->field, 'invalid' );
326
327 if ( $invalid_message === __( 'The reCAPTCHA was not entered correctly', 'formidable' ) ) {
328 $invalid_message = '';
329 }
330
331 $errors[ 'field' . $args['id'] ] = $invalid_message === '' ? $frm_settings->re_msg : $invalid_message;
332
333 return $errors;
334 }
335
336 /**
337 * @param float $score
338 *
339 * @return void
340 */
341 private function set_score( $score ) {
342 global $frm_vars;
343
344 if ( ! isset( $frm_vars['captcha_scores'] ) ) {
345 $frm_vars['captcha_scores'] = array();
346 }
347
348 $form_id = is_object( $this->field ) ? $this->field->form_id : $this->field['form_id'];
349
350 if ( ! isset( $frm_vars['captcha_scores'][ $form_id ] ) ) {
351 $frm_vars['captcha_scores'][ $form_id ] = $score;
352 }
353 }
354
355 /**
356 * @param array $args
357 *
358 * @return array
359 */
360 public function validate( $args ) {
361 if ( ! $this->should_validate() ) {
362 return array();
363 }
364
365 $missing_token = ! self::post_data_includes_token();
366
367 if ( $missing_token ) {
368 return array( 'field' . $args['id'] => __( 'The captcha is missing from this form', 'formidable' ) );
369 }
370
371 return $this->validate_against_api( $args );
372 }
373
374 /**
375 * @since 6.8.4
376 *
377 * @return bool
378 */
379 protected static function post_data_includes_token() {
380 $settings = FrmCaptchaFactory::get_settings_object();
381 // phpcs:ignore WordPress.Security.NonceVerification.Missing
382 return ! empty( $_POST[ $settings->token_field ] );
383 }
384
385 /**
386 * Check if the active captcha type's public key is set.
387 *
388 * @since 4.07
389 *
390 * @return bool
391 */
392 public static function should_show_captcha() {
393 $settings = FrmCaptchaFactory::get_settings_object();
394 return $settings->has_pubkey();
395 }
396
397 /**
398 * @return bool
399 */
400 protected function should_validate() {
401 $is_hidden_field = apply_filters( 'frm_is_field_hidden', false, $this->field, wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
402
403 if ( FrmAppHelper::is_admin() || $is_hidden_field ) {
404 return false;
405 }
406
407 // Don't require the captcha if it shouldn't be shown
408 return self::should_show_captcha();
409 }
410
411 /**
412 * @return array|WP_Error
413 */
414 protected function send_api_check() {
415 $captcha_settings = FrmCaptchaFactory::get_settings_object();
416 $arg_array = array(
417 'body' => array(
418 'secret' => $captcha_settings->secret,
419 'response' => FrmAppHelper::get_param( $captcha_settings->token_field, '', 'post', 'sanitize_text_field' ),
420 'remoteip' => FrmAppHelper::get_ip_address(),
421 ),
422 );
423
424 return wp_remote_post( $captcha_settings->endpoint, $arg_array );
425 }
426
427 /**
428 * Updates field name in page builder to the currently activated captcha if it is set to the default.
429 *
430 * @since 6.0
431 *
432 * @param array $values
433 *
434 * @return array Values.
435 */
436 public static function update_field_name( $values ) {
437 if ( $values['type'] === 'captcha' ) {
438 $name = $values['name'];
439
440 if ( in_array( $name, array( __( 'reCAPTCHA', 'formidable' ), __( 'hCaptcha', 'formidable' ) ), true ) ) {
441 $values['name'] = __( 'Captcha', 'formidable' );
442 }
443 }
444
445 return $values;
446 }
447 }
448