PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.26.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.26.1
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.26.1, at classes/models/fields/FrmFieldCaptcha.php

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