PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / includes / admin / security / modules / captcha / class-charitable-captcha-hcaptcha.php

class-charitable-captcha-hcaptcha.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/admin/security/modules/captcha/class-charitable-captcha-hcaptcha.php

427 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The main Charitable CAPTCHA class.
4 *
5 * @package Charitable/Classes/Charitable_Captcha_HCaptcha
6 * @copyright Copyright (c) 2023, WP Charitable LLC
7 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
8 * @version 1.8.9
9 * @since 1.8.9
10 */
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 if ( ! class_exists( 'Charitable_Captcha_HCaptcha' ) ) :
18
19 /**
20 * hCaptcha module.
21 *
22 * @since 1.8.9
23 */
24 class Charitable_Captcha_HCaptcha {
25
26 /**
27 * Class constructor.
28 *
29 * @since 1.8.9
30 */
31 public function __construct() {
32 if ( $this->is_active() ) {
33 $this->setup();
34 }
35 }
36
37 /**
38 * Return whether the module is active.
39 *
40 * @since 1.8.0
41 *
42 * @return boolean
43 */
44 public function is_active() {
45 return 'hcaptcha' === charitable_get_option( 'captcha_provider' )
46 && ! empty( $this->get_sitekey() )
47 && ! empty( $this->get_secret_key() );
48 }
49
50 /**
51 * Get the module settings.
52 *
53 * @since 1.8.0
54 *
55 * @return array
56 */
57 public function get_settings() {
58 return array(
59 'hcaptcha_sitekey' => array(
60 'title' => __( 'hCaptcha Site Key', 'charitable' ),
61 'type' => 'text',
62 'class' => 'wide',
63 'help' => __( 'Your hCaptcha "Sitekey" setting. Find this in <a href="https://dashboard.hcaptcha.com/sites" target="_blank">your hCaptcha dashboard</a>.', 'charitable' ),
64 'priority' => 144,
65 'attrs' => array(
66 'data-trigger-key' => '#charitable_settings_captcha_provider',
67 'data-trigger-value' => 'hcaptcha',
68 'style' => 'width: 100%;',
69 ),
70 ),
71 'hcaptcha_secret_key' => array(
72 'title' => __( 'hCaptcha Secret Key', 'charitable' ),
73 'type' => 'password',
74 'class' => 'wide',
75 'help' => __( 'Your hCaptcha Secret Key setting can be found in the Settings page in your hCaptcha dashboard.', 'charitable' ),
76 'priority' => 148,
77 'attrs' => array(
78 'data-trigger-key' => '#charitable_settings_captcha_provider',
79 'data-trigger-value' => 'hcaptcha',
80 'style' => 'width: 100%;',
81 ),
82 ),
83 );
84 }
85
86 /**
87 * Get the sitekey.
88 *
89 * @since 1.8.0
90 *
91 * @return string
92 */
93 public function get_sitekey() {
94 return charitable_get_option( 'hcaptcha_sitekey' );
95 }
96
97 /**
98 * Get the secret key.
99 *
100 * @since 1.8.0
101 *
102 * @return string
103 */
104 public function get_secret_key() {
105 return charitable_get_option( 'hcaptcha_secret_key' );
106 }
107
108 /**
109 * Set up module hooks.
110 *
111 * @since 1.8.0
112 *
113 * @return void
114 */
115 public function setup() {
116 add_action( 'wp_enqueue_scripts', array( $this, 'add_scripts' ), 15 );
117 add_action( 'charitable_form_after_fields', array( $this, 'add_hcaptcha_to_form' ) );
118
119 /* For the donation form, validate token as part of the security check. */
120 add_filter( 'charitable_validate_donation_form_submission_security_check', array( $this, 'validate_token_for_donation' ), 10, 2 );
121
122 /**
123 * For the password retrieval, password reset, profile and registration
124 * forms, we check hcaptcha before the regular form processor occurs.
125 *
126 * If the hcaptcha check fails, we prevent further processing.
127 */
128 add_action( 'charitable_retrieve_password', array( $this, 'check_recaptcha_before_form_processing' ), 1 );
129 add_action( 'charitable_reset_password', array( $this, 'check_recaptcha_before_form_processing' ), 1 );
130 add_action( 'charitable_update_profile', array( $this, 'check_recaptcha_before_form_processing' ), 1 );
131 add_action( 'charitable_save_registration', array( $this, 'check_recaptcha_before_form_processing' ), 1 );
132 }
133
134 /**
135 * Add hCaptcha scripts.
136 *
137 * @since 1.8.0
138 *
139 * @return void
140 */
141 public function add_scripts() {
142 wp_register_script( 'charitable-hcaptcha', charitable()->get_path( 'assets', false ) . 'js/security/charitable-hcaptcha.js', array( 'jquery' ), charitable()->get_version(), true );
143 wp_enqueue_script( 'charitable-hcaptcha' );
144 wp_localize_script(
145 'charitable-hcaptcha',
146 'CHARITABLE_HCAPTCHA',
147 array(
148 'sitekey' => $this->get_sitekey(),
149 'error_message' => __( 'Your form submission failed because the captcha failed to be validated.', 'charitable' ),
150 )
151 );
152 }
153
154 /**
155 * Add hCaptcha widget to the form.
156 *
157 * @since 1.8.0
158 *
159 * @param Charitable_Form $form A form object.
160 * @return void
161 */
162 public function add_hcaptcha_to_form( Charitable_Form $form ) {
163 /* Don't show captcha for logged in users. */
164 if ( is_user_logged_in() && 'no' === charitable_get_option( 'captcha_logged_in', 'no' ) ) {
165 return;
166 }
167
168 if ( $this->is_enabled_for_form( $this->get_current_form_from_class( $form ) ) ) {
169 ob_start();
170 ?>
171 <div class="charitable-hcaptcha" id="hcaptcha-<?php echo esc_attr( $form->get_form_identifier() ); ?>" data-sitekey="<?php echo esc_attr( $this->get_sitekey() ); ?>"></div>
172 <input name="hcaptcha_token" type="hidden" value="" />
173 <script src="https://hcaptcha.com/1/api.js?onload=charitable_hcaptcha_onload&render=explicit" async defer></script> <?php // phpcs:ignore ?>
174 <?php
175 echo ob_get_clean(); // phpcs:ignore
176 }
177 }
178
179 /**
180 * Return the current form key based on the class name.
181 *
182 * @since 1.8.0
183 *
184 * @param Charitable_Form $form A form object.
185 * @return string|null Form key if it's a supported form. Null otherwise.
186 */
187 public function get_current_form_from_class( Charitable_Form $form ) {
188 switch ( get_class( $form ) ) {
189 case 'Charitable_Registration_Form':
190 $form_key = 'registration_form';
191 break;
192
193 case 'Charitable_Profile_Form':
194 $form_key = 'profile_form';
195 break;
196
197 case 'Charitable_Forgot_Password_Form':
198 $form_key = 'password_retrieval_form';
199 break;
200
201 case 'Charitable_Reset_Password_Form':
202 $form_key = 'password_reset_form';
203 break;
204
205 case 'Charitable_Donation_Form':
206 $form_key = 'donation_form';
207 break;
208
209 case 'Charitable_Donation_Amount_Form':
210 $form_key = 'donation_amount_form';
211 break;
212
213 case 'Charitable_Ambassadors_Campaign_Form':
214 $form_key = 'campaign_form';
215 break;
216
217 default:
218 $form_key = null;
219 }
220
221 return $form_key;
222 }
223
224 /**
225 * Return the form key based on the hook.
226 *
227 * @since 1.8.0
228 *
229 * @return string|null
230 */
231 public function get_current_form_from_hook() {
232 switch ( current_filter() ) {
233 case 'charitable_save_registration':
234 $form_key = 'registration_form';
235 break;
236
237 case 'charitable_update_profile':
238 $form_key = 'profile_form';
239 break;
240
241 case 'charitable_retrieve_password':
242 $form_key = 'password_retrieval_form';
243 break;
244
245 case 'charitable_reset_password':
246 $form_key = 'password_reset_form';
247 break;
248
249 case 'charitable_save_campaign':
250 $form_key = 'campaign_form';
251 break;
252
253 default:
254 $form_key = null;
255 }
256
257 return $form_key;
258 }
259
260 /**
261 * Returns whether hCaptcha is enabled for the given form.
262 *
263 * @since 1.8.0
264 *
265 * @param string|null $form_key The key of the form, or NULL if it's not a supported one.
266 * @return boolean
267 */
268 public function is_enabled_for_form( $form_key ) {
269 if ( is_null( $form_key ) ) {
270 return false;
271 }
272
273 $forms = $this->get_form_settings();
274
275 return isset( $forms[ $form_key ] ) && $forms[ $form_key ];
276 }
277
278 /**
279 * Returns an array of forms that hCaptcha can be enabled for.
280 *
281 * @since 1.8.0
282 *
283 * @return array
284 */
285 public function get_form_settings() {
286 /**
287 * Returns an array of forms along with whether hCaptcha
288 * is enabled for them. By default, all forms are enabled.
289 *
290 * @since 1.8.9
291 *
292 * @param array $forms All the supported forms in a key=>value array, where the value is either
293 * true (hCaptcha is enabled) or false (hCaptcha is disabled).
294 */
295 return apply_filters(
296 'charitable_hcaptcha_forms',
297 array(
298 'donation_form' => true,
299 'donation_amount_form' => false,
300 'registration_form' => true,
301 'password_reset_form' => true,
302 'password_retrieval_form' => true,
303 'profile_form' => false,
304 'campaign_form' => true,
305 )
306 );
307 }
308
309 /**
310 * Validate the hCaptcha token on donation form submission.
311 *
312 * @since 1.8.0
313 *
314 * @param boolean $ret The result to be returned. True or False.
315 * @param Charitable_Donation_Form $form The donation form object.
316 * @return boolean
317 */
318 public function validate_token_for_donation( $ret, Charitable_Donation_Form $form ) {
319 if ( ! $ret ) {
320 return $ret;
321 }
322
323 /* Don't show captcha for logged in users. */
324 if ( is_user_logged_in() && 'no' === charitable_get_option( 'captcha_logged_in', 'no' ) ) {
325 return $ret;
326 }
327
328 if ( ! $this->is_enabled_for_form( $this->get_current_form_from_class( $form ) ) ) {
329 return $ret;
330 }
331
332 return $this->is_captcha_valid();
333 }
334
335 /**
336 * Check token validity before processing a form submission.
337 *
338 * If the check fails, form processing is blocked.
339 *
340 * @since 1.8.0
341 *
342 * @return void
343 */
344 public function check_recaptcha_before_form_processing() {
345 /* Don't show captcha for logged in users. */
346 if ( is_user_logged_in() && 'no' === charitable_get_option( 'captcha_logged_in', 'no' ) ) {
347 return;
348 }
349
350 $form_key = $this->get_current_form_from_hook();
351
352 if ( ! $this->is_enabled_for_form( $form_key ) ) {
353 return;
354 }
355
356 /* Captcha token isn't valid. */
357 if ( ! $this->is_captcha_valid() ) {
358 switch ( $form_key ) {
359 case 'registration_form':
360 remove_action( 'charitable_save_registration', array( 'Charitable_Registration_Form', 'save_registration' ) );
361 break;
362
363 case 'profile_form':
364 remove_action( 'charitable_update_profile', array( 'Charitable_Profile_Form', 'update_profile' ) );
365 break;
366
367 case 'password_retrieval_form':
368 remove_action( 'charitable_retrieve_password', array( 'Charitable_Forgot_Password_Form', 'retrieve_password' ) );
369 break;
370
371 case 'password_reset_form':
372 remove_action( 'charitable_reset_password', array( 'Charitable_Reset_Password_Form', 'reset_password' ) );
373 break;
374
375 case 'campaign_form':
376 remove_action( 'charitable_save_campaign', array( 'Charitable_Ambassadors_Campaign_Form', 'save_campaign' ) );
377 break;
378 }
379 }
380 }
381
382 /**
383 * Returns whether the captcha is valid.
384 *
385 * @since 1.8.0
386 *
387 * @return boolean
388 */
389 public function is_captcha_valid() {
390 if ( ! isset( $_POST['hcaptcha_token'] ) || empty( $_POST['hcaptcha_token'] ) ) { // phpcs:ignore
391 charitable_get_notices()->add_error( __( 'Missing captcha token.', 'charitable' ) );
392 return false;
393 }
394
395 $token = sanitize_text_field( wp_unslash( $_POST['hcaptcha_token'] ) ); // phpcs:ignore
396
397 $response = wp_remote_post(
398 'https://hcaptcha.com/siteverify',
399 array(
400 'body' => array(
401 'secret' => $this->get_secret_key(),
402 'response' => $token,
403 'remoteip' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '', // phpcs:ignore
404 'sitekey' => $this->get_sitekey(),
405 ),
406 )
407 );
408
409 if ( is_wp_error( $response ) ) {
410 charitable_get_notices()->add_error( __( 'Failed to verify captcha.', 'charitable' ) );
411 return false;
412 }
413
414 $result = json_decode( wp_remote_retrieve_body( $response ), true );
415
416 if ( ! isset( $result['success'] ) || ! $result['success'] ) {
417 charitable_get_notices()->add_error( __( 'Captcha validation failed.', 'charitable' ) );
418 return false;
419 }
420
421 return true;
422 }
423 }
424
425 endif;
426
427