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

446 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' && 'v3' === $frm_settings->re_type && array_key_exists( 'score', $response ) ) {
308 $threshold = floatval( $frm_settings->re_threshold );
309 $score = floatval( $response['score'] );
310
311 $this->set_score( $score );
312
313 if ( $score < $threshold ) {
314 $response['success'] = false;
315 }
316 }
317
318 if ( ! isset( $response['success'] ) || $response['success'] ) {
319 return $errors;
320 }
321
322 // What happens when the CAPTCHA was entered incorrectly
323 $invalid_message = FrmField::get_option( $this->field, 'invalid' );
324
325 if ( $invalid_message === __( 'The reCAPTCHA was not entered correctly', 'formidable' ) ) {
326 $invalid_message = '';
327 }
328
329 $errors[ 'field' . $args['id'] ] = $invalid_message === '' ? $frm_settings->re_msg : $invalid_message;
330
331 return $errors;
332 }
333
334 /**
335 * @param float $score
336 *
337 * @return void
338 */
339 private function set_score( $score ) {
340 global $frm_vars;
341
342 if ( ! isset( $frm_vars['captcha_scores'] ) ) {
343 $frm_vars['captcha_scores'] = array();
344 }
345
346 $form_id = is_object( $this->field ) ? $this->field->form_id : $this->field['form_id'];
347
348 if ( ! isset( $frm_vars['captcha_scores'][ $form_id ] ) ) {
349 $frm_vars['captcha_scores'][ $form_id ] = $score;
350 }
351 }
352
353 /**
354 * @param array $args
355 *
356 * @return array
357 */
358 public function validate( $args ) {
359 if ( ! $this->should_validate() ) {
360 return array();
361 }
362
363 $missing_token = ! self::post_data_includes_token();
364
365 if ( $missing_token ) {
366 return array( 'field' . $args['id'] => __( 'The captcha is missing from this form', 'formidable' ) );
367 }
368
369 return $this->validate_against_api( $args );
370 }
371
372 /**
373 * @since 6.8.4
374 *
375 * @return bool
376 */
377 protected static function post_data_includes_token() {
378 $settings = FrmCaptchaFactory::get_settings_object();
379 // phpcs:ignore WordPress.Security.NonceVerification.Missing
380 return ! empty( $_POST[ $settings->token_field ] );
381 }
382
383 /**
384 * Check if the active captcha type's public key is set.
385 *
386 * @since 4.07
387 *
388 * @return bool
389 */
390 public static function should_show_captcha() {
391 $settings = FrmCaptchaFactory::get_settings_object();
392 return $settings->has_pubkey();
393 }
394
395 /**
396 * @return bool
397 */
398 protected function should_validate() {
399 $is_hidden_field = apply_filters( 'frm_is_field_hidden', false, $this->field, wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
400
401 if ( FrmAppHelper::is_admin() || $is_hidden_field ) {
402 return false;
403 }
404
405 // Don't require the captcha if it shouldn't be shown
406 return self::should_show_captcha();
407 }
408
409 /**
410 * @return array|WP_Error
411 */
412 protected function send_api_check() {
413 $captcha_settings = FrmCaptchaFactory::get_settings_object();
414 $arg_array = array(
415 'body' => array(
416 'secret' => $captcha_settings->secret,
417 'response' => FrmAppHelper::get_param( $captcha_settings->token_field, '', 'post', 'sanitize_text_field' ),
418 'remoteip' => FrmAppHelper::get_ip_address(),
419 ),
420 );
421
422 return wp_remote_post( $captcha_settings->endpoint, $arg_array );
423 }
424
425 /**
426 * Updates field name in page builder to the currently activated captcha if it is set to the default.
427 *
428 * @since 6.0
429 *
430 * @param array $values
431 *
432 * @return array Values.
433 */
434 public static function update_field_name( $values ) {
435 if ( $values['type'] === 'captcha' ) {
436 $name = $values['name'];
437
438 if ( in_array( $name, array( __( 'reCAPTCHA', 'formidable' ), __( 'hCaptcha', 'formidable' ) ), true ) ) {
439 $values['name'] = __( 'Captcha', 'formidable' );
440 }
441 }
442
443 return $values;
444 }
445 }
446