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

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