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

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