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

444 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 wp_register_script( 'captcha-api', $this->api_url(), array( 'formidable' ), '3', true );
145 wp_enqueue_script( 'captcha-api' );
146 }
147
148 /**
149 * Get the URL for the script JS that is loaded on the front end.
150 *
151 * @return string
152 */
153 protected function api_url() {
154 $frm_settings = FrmAppHelper::get_settings();
155 $active_mode = $frm_settings->active_captcha;
156
157 if ( 'recaptcha' === $active_mode ) {
158 return $this->recaptcha_api_url( $frm_settings );
159 }
160
161 if ( 'hcaptcha' === $active_mode ) {
162 return $this->hcaptcha_api_url();
163 }
164
165 return $this->turnstile_api_url();
166 }
167
168 /**
169 * @param FrmSettings $frm_settings
170 *
171 * @return string
172 */
173 protected function recaptcha_api_url( $frm_settings ) {
174 $api_js_url = 'https://www.google.com/recaptcha/api.js?';
175
176 if ( $this->allow_multiple( $frm_settings ) ) {
177 $api_js_url .= '&onload=frmRecaptcha&render=explicit';
178 }
179
180 $lang = apply_filters( 'frm_recaptcha_lang', $frm_settings->re_lang, $this->field );
181
182 if ( $lang ) {
183 $api_js_url .= '&hl=' . $lang;
184 }
185
186 // Since this URL initially ends with ? and we never use add_query_arg, remove the extra
187 // & that appears immediately after the ?
188 $api_js_url = str_replace( '?&', '?', $api_js_url );
189
190 /**
191 * @param string $api_js_url
192 */
193 return apply_filters( 'frm_recaptcha_js_url', $api_js_url );
194 }
195
196 /**
197 * @since 6.0
198 *
199 * @return string
200 */
201 protected function hcaptcha_api_url() {
202 $api_js_url = 'https://js.hcaptcha.com/1/api.js';
203 $lang = $this->get_captcha_language();
204
205 if ( $lang ) {
206 // 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.
207 $lang_parts = explode( '-', $lang );
208 $api_js_url .= '?hl=' . $lang_parts[0];
209 }
210
211 $api_js_url = add_query_arg( 'onload', 'frmHcaptcha', $api_js_url );
212
213 /**
214 * Allows updating hcaptcha js api url.
215 *
216 * @since 6.0
217 *
218 * @param string $api_js_url
219 */
220 return apply_filters( 'frm_hcaptcha_js_url', $api_js_url );
221 }
222
223 /**
224 * @since 6.8.4
225 *
226 * @return string
227 */
228 protected function turnstile_api_url() {
229 $api_js_url = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=frmTurnstile&render=explicit';
230
231 /**
232 * Allows updating hcaptcha js api url.
233 *
234 * @since 6.8.4
235 *
236 * @param string $api_js_url
237 */
238 $api_js_url = apply_filters( 'frm_turnstile_js_url', $api_js_url );
239
240 // Prevent render=explicit from happening twice in case someone patched
241 // The double rendering issue using the frm_turnstile_js_url hook.
242 return str_replace(
243 '&render=explicit&render=explicit',
244 '&render=explicit',
245 $api_js_url
246 );
247 }
248
249 /**
250 * @param FrmSettings $frm_settings
251 *
252 * @return string
253 *
254 * @psalm-return ''|'frm-'
255 */
256 protected function class_prefix( $frm_settings ) {
257 return FrmCaptchaFactory::get_settings_object()->get_class_prefix( $this->allow_multiple( $frm_settings ) );
258 }
259
260 /**
261 * @param FrmSettings $frm_settings This isn't used anymore. It's only there for backwards compatibility.
262 *
263 * @return string
264 *
265 * @psalm-return 'g-recaptcha'|'h-captcha'
266 */
267 protected function captcha_class( $frm_settings ) {
268 $settings = FrmCaptchaFactory::get_settings_object();
269 return $settings->get_element_class_name();
270 }
271
272 /**
273 * @param FrmSettings $frm_settings
274 *
275 * @return bool
276 */
277 protected function allow_multiple( $frm_settings ) {
278 return $frm_settings->re_multi;
279 }
280
281 /**
282 * @since 4.07
283 *
284 * @param array $args
285 *
286 * @return array
287 */
288 protected function validate_against_api( $args ) {
289 $errors = array();
290 $frm_settings = FrmAppHelper::get_settings();
291 $resp = $this->send_api_check();
292 $response = json_decode( wp_remote_retrieve_body( $resp ), true );
293
294 if ( is_wp_error( $resp ) ) {
295 $error_string = $resp->get_error_message();
296 $errors[ 'field' . $args['id'] ] = __( 'There was a problem verifying your captcha', 'formidable' );
297 $errors[ 'field' . $args['id'] ] .= ' ' . $error_string;
298 return $errors;
299 }
300
301 if ( ! is_array( $response ) ) {
302 return $errors;
303 }
304
305 if ( $frm_settings->active_captcha === 'recaptcha' && 'v3' === $frm_settings->re_type && array_key_exists( 'score', $response ) ) {
306 $threshold = floatval( $frm_settings->re_threshold );
307 $score = floatval( $response['score'] );
308
309 $this->set_score( $score );
310
311 if ( $score < $threshold ) {
312 $response['success'] = false;
313 }
314 }
315
316 if ( ! isset( $response['success'] ) || $response['success'] ) {
317 return $errors;
318 }
319
320 // What happens when the CAPTCHA was entered incorrectly
321 $invalid_message = FrmField::get_option( $this->field, 'invalid' );
322
323 if ( $invalid_message === __( 'The reCAPTCHA was not entered correctly', 'formidable' ) ) {
324 $invalid_message = '';
325 }
326
327 $errors[ 'field' . $args['id'] ] = $invalid_message === '' ? $frm_settings->re_msg : $invalid_message;
328
329 return $errors;
330 }
331
332 /**
333 * @param float $score
334 *
335 * @return void
336 */
337 private function set_score( $score ) {
338 global $frm_vars;
339
340 if ( ! isset( $frm_vars['captcha_scores'] ) ) {
341 $frm_vars['captcha_scores'] = array();
342 }
343
344 $form_id = is_object( $this->field ) ? $this->field->form_id : $this->field['form_id'];
345
346 if ( ! isset( $frm_vars['captcha_scores'][ $form_id ] ) ) {
347 $frm_vars['captcha_scores'][ $form_id ] = $score;
348 }
349 }
350
351 /**
352 * @param array $args
353 *
354 * @return array
355 */
356 public function validate( $args ) {
357 if ( ! $this->should_validate() ) {
358 return array();
359 }
360
361 $missing_token = ! self::post_data_includes_token();
362
363 if ( $missing_token ) {
364 return array( 'field' . $args['id'] => __( 'The captcha is missing from this form', 'formidable' ) );
365 }
366
367 return $this->validate_against_api( $args );
368 }
369
370 /**
371 * @since 6.8.4
372 *
373 * @return bool
374 */
375 protected static function post_data_includes_token() {
376 $settings = FrmCaptchaFactory::get_settings_object();
377 // phpcs:ignore WordPress.Security.NonceVerification.Missing
378 return ! empty( $_POST[ $settings->token_field ] );
379 }
380
381 /**
382 * Check if the active captcha type's public key is set.
383 *
384 * @since 4.07
385 *
386 * @return bool
387 */
388 public static function should_show_captcha() {
389 $settings = FrmCaptchaFactory::get_settings_object();
390 return $settings->has_pubkey();
391 }
392
393 /**
394 * @return bool
395 */
396 protected function should_validate() {
397 $is_hidden_field = apply_filters( 'frm_is_field_hidden', false, $this->field, wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
398
399 if ( FrmAppHelper::is_admin() || $is_hidden_field ) {
400 return false;
401 }
402
403 // Don't require the captcha if it shouldn't be shown
404 return self::should_show_captcha();
405 }
406
407 /**
408 * @return array|WP_Error
409 */
410 protected function send_api_check() {
411 $captcha_settings = FrmCaptchaFactory::get_settings_object();
412 $arg_array = array(
413 'body' => array(
414 'secret' => $captcha_settings->secret,
415 'response' => FrmAppHelper::get_param( $captcha_settings->token_field, '', 'post', 'sanitize_text_field' ),
416 'remoteip' => FrmAppHelper::get_ip_address(),
417 ),
418 );
419
420 return wp_remote_post( $captcha_settings->endpoint, $arg_array );
421 }
422
423 /**
424 * Updates field name in page builder to the currently activated captcha if it is set to the default.
425 *
426 * @since 6.0
427 *
428 * @param array $values
429 *
430 * @return array Values.
431 */
432 public static function update_field_name( $values ) {
433 if ( $values['type'] === 'captcha' ) {
434 $name = $values['name'];
435
436 if ( in_array( $name, array( __( 'reCAPTCHA', 'formidable' ), __( 'hCaptcha', 'formidable' ) ), true ) ) {
437 $values['name'] = __( 'Captcha', 'formidable' );
438 }
439 }
440
441 return $values;
442 }
443 }
444