PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.9.0
Defender Security – Malware Scanner, Login Security & Firewall v5.9.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / model / setting / class-captcha.php

class-captcha.php in Defender Security – Malware Scanner, Login Security & Firewall 5.9.0, at src/model/setting/class-captcha.php

448 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handle Captcha settings.
4 *
5 * @package WP_Defender\Model\Setting
6 */
7
8 namespace WP_Defender\Model\Setting;
9
10 use Calotes\Model\Setting;
11
12 /**
13 * Model for Captcha settings.
14 */
15 class Captcha extends Setting {
16 /**
17 * Turnstile provider key.
18 */
19 const TURNSTILE = 'turnstile';
20
21 /**
22 * Option name.
23 * We'll keep the current table for all Captcha for backward compatibility. There's no need to edit the table name to store settings in the database.
24 *
25 * @var string
26 */
27 protected $table = 'wd_recaptcha_settings';
28
29 /**
30 * Feature status.
31 *
32 * @var bool
33 * @defender_property
34 */
35 public $enabled = false;
36
37 /**
38 * Captcha Provider.
39 *
40 * @var string
41 * @defender_property
42 */
43 public $provider = 'recaptcha';
44
45 /**
46 * Active Captcha type.
47 *
48 * @var string
49 * @defender_property
50 * @rule required
51 * @rule in[v2_checkbox,v2_invisible,v3_recaptcha,turnstile]
52 */
53 public $active_type = 'v2_checkbox';
54
55 /**
56 * Data for v2 checkbox reCaptcha.
57 *
58 * @var array
59 * @defender_property
60 */
61 public $data_v2_checkbox;
62
63 /**
64 * Data for v2 invisible reCaptcha.
65 *
66 * @var array
67 * @defender_property
68 */
69 public $data_v2_invisible;
70
71 /**
72 * Data for v3 reCaptcha.
73 *
74 * @var array
75 * @defender_property
76 */
77 public $data_v3_recaptcha;
78
79 /**
80 * Data for Cloudflare Turnstile.
81 *
82 * @var array
83 * @defender_property
84 */
85 public $data_turnstile;
86
87 /**
88 * Language for Captcha.
89 *
90 * @var string
91 * @defender_property
92 * @rule required
93 */
94 public $language = '';
95
96 /**
97 * Message for reCaptcha.
98 *
99 * @var string
100 * @defender_property
101 * @sanitize sanitize_textarea_field
102 */
103 public $message = '';
104
105 /**
106 * Locations for Captcha.
107 *
108 * @var array
109 * @defender_property
110 * @rule required
111 */
112 public $locations = array();
113
114 /**
115 * Flag to detect WooCommerce.
116 *
117 * @var bool
118 * @defender_property
119 */
120 public $detect_woo = false;
121
122 /**
123 * Checked locations for WooCommerce.
124 *
125 * @var array
126 * @defender_property
127 * @rule required
128 */
129 public $woo_checked_locations = array();
130
131 /**
132 * Flag to detect BuddyPress.
133 *
134 * @var bool
135 * @defender_property
136 */
137 public $detect_buddypress = false;
138
139 /**
140 * Checked locations for BuddyPress.
141 *
142 * @var array
143 * @defender_property
144 * @rule required
145 */
146 public $buddypress_checked_locations = array();
147
148 /**
149 * Flag to disable Captcha for known users.
150 *
151 * @var bool
152 * @defender_property
153 */
154 public $disable_for_known_users = true;
155
156 /**
157 * Rules for validating the Captcha settings.
158 *
159 * @var array
160 */
161 protected $rules = array(
162 array( array( 'enabled', 'detect_woo', 'detect_buddypress' ), 'boolean' ),
163 array( array( 'active_type' ), 'in', array( 'v2_checkbox', 'v2_invisible', 'v3_recaptcha', self::TURNSTILE ) ),
164 );
165
166 /**
167 * Retrieves the default values for the Captcha settings.
168 *
169 * @return array An associative array containing the default values.
170 */
171 public function get_default_values(): array {
172 return array(
173 'message' => esc_html__( 'reCAPTCHA verification failed. Please try again.', 'defender-security' ),
174 'turnstile_message' => esc_html__( 'Turnstile verification failed. Please try again.', 'defender-security' ),
175 );
176 }
177
178 /**
179 * Load default values.
180 *
181 * @return void
182 */
183 protected function before_load(): void {
184 $default_values = $this->get_default_values();
185 $this->provider = 'recaptcha';
186 $this->message = $default_values['message'];
187 $this->language = 'automatic';
188 $this->data_v2_checkbox = array(
189 'key' => '',
190 'secret' => '',
191 'size' => 'normal',
192 'style' => 'light',
193 );
194 $this->data_v2_invisible = array(
195 'key' => '',
196 'secret' => '',
197 );
198 $this->data_v3_recaptcha = array(
199 'key' => '',
200 'secret' => '',
201 'threshold' => '0.5',
202 );
203 $this->data_turnstile = array(
204 'key' => '',
205 'secret' => '',
206 'size' => 'normal',
207 'style' => 'auto',
208 'message' => $default_values['turnstile_message'],
209 'language' => 'auto',
210 );
211 }
212
213 /**
214 * Checks if the given Captcha type is valid and has all the necessary data.
215 *
216 * @param string $active_type The Captcha type to check.
217 *
218 * @return bool Returns true if the Captcha type is valid and has all the necessary data, false otherwise.
219 */
220 private function check_captcha_type( string $active_type ): bool {
221 if (
222 'v2_checkbox' === $active_type
223 && '' !== $this->data_v2_checkbox['key']
224 && '' !== $this->data_v2_checkbox['secret']
225 ) {
226 return true;
227 } elseif (
228 'v2_invisible' === $active_type
229 && '' !== $this->data_v2_invisible['key']
230 && '' !== $this->data_v2_invisible['secret']
231 ) {
232 return true;
233 } elseif (
234 'v3_recaptcha' === $active_type
235 && '' !== $this->data_v3_recaptcha['key'] && '' !== $this->data_v3_recaptcha['secret']
236 ) {
237 return true;
238 } elseif (
239 self::TURNSTILE === $active_type &&
240 '' !== $this->data_turnstile['key'] &&
241 '' !== $this->data_turnstile['secret']
242 ) {
243 return true;
244 } else {
245 return false;
246 }
247 }
248
249 /**
250 * Checks if the CAPTCHA is active.
251 *
252 * @return bool Returns true if the CAPTCHA is active, false otherwise.
253 */
254 public function is_active(): bool {
255 $hook_name = self::TURNSTILE === $this->active_type ? 'wd_turnstile_enable' : 'wd_recaptcha_enable';
256 $enable = apply_filters(
257 $hook_name,
258 $this->enabled
259 && '' !== $this->active_type
260 && '' !== $this->language
261 // For each Captcha type.
262 && $this->check_captcha_type( $this->active_type )
263 );
264
265 return is_bool( $enable ) ? $enable : (bool) $enable;
266 }
267
268 /**
269 * Is activated any default location?
270 *
271 * @return bool
272 */
273 public function enable_default_location(): bool {
274 return array() !== $this->locations;
275 }
276
277 /**
278 * Level#2 check by any activated location. Only if the plugin is enabled.
279 *
280 * @return bool
281 */
282 public function enable_woo_location(): bool {
283 return $this->detect_woo && array() !== $this->woo_checked_locations;
284 }
285
286 /**
287 * Level#2 check by deactivated locations. Only if the plugin is enabled.
288 *
289 * @return bool
290 */
291 public function is_unchecked_woo_locations(): bool {
292 return $this->detect_woo && array() === $this->woo_checked_locations;
293 }
294
295 /**
296 * Level#1 check. If the plugin is disabled, there is no point further.
297 *
298 * @param bool $is_woo_activated Whether WooCommerce is activated or not.
299 *
300 * @return bool
301 */
302 public function check_woo_locations( $is_woo_activated ): bool {
303 if ( ! $is_woo_activated ) {
304 return false;
305 }
306
307 return $this->enable_woo_location();
308 }
309
310 /**
311 * Level#2 check by any activated location. Only if the plugin is enabled.
312 *
313 * @return bool
314 */
315 public function enable_buddypress_location(): bool {
316 return $this->detect_buddypress && array() !== $this->buddypress_checked_locations;
317 }
318
319 /**
320 * Level#2 check by deactivated locations. Only if the plugin is enabled.
321 *
322 * @return bool
323 */
324 public function is_unchecked_buddypress_locations(): bool {
325 return $this->detect_buddypress && array() === $this->buddypress_checked_locations;
326 }
327
328 /**
329 * Level#1 Checks if the BuddyPress locations are valid and have all the necessary data.
330 *
331 * @param bool $is_buddypress_activated Whether BuddyPress is activated or not.
332 *
333 * @return bool Returns true if the BuddyPress locations are valid and have all the necessary data, false otherwise.
334 */
335 public function check_buddypress_locations( $is_buddypress_activated ): bool {
336 if ( ! $is_buddypress_activated ) {
337 return false;
338 }
339
340 return $this->enable_buddypress_location();
341 }
342
343 /**
344 * Define settings labels.
345 *
346 * @return array
347 */
348 public function labels(): array {
349 return array(
350 'enabled' => self::get_module_name(),
351 'active_type' => esc_html__( 'Configure CAPTCHA', 'defender-security' ),
352 'v2_checkbox' => esc_html__( 'V2 Checkbox', 'defender-security' ),
353 'v2_invisible' => esc_html__( 'V2 Invisible', 'defender-security' ),
354 'v3_recaptcha' => esc_html__( 'reCAPTCHA V3', 'defender-security' ),
355 'language' => esc_html__( 'Language', 'defender-security' ),
356 'message' => esc_html__( 'Error Message', 'defender-security' ),
357 'locations' => esc_html__( 'CAPTCHA Locations', 'defender-security' ),
358 'detect_woo' => esc_html__( 'WooCommerce', 'defender-security' ),
359 'detect_buddypress' => esc_html__( 'BuddyPress', 'defender-security' ),
360 'disable_for_known_users' => esc_html__( 'Disable for logged in users', 'defender-security' ),
361 );
362 }
363
364 /**
365 * Validates the form after submission.
366 *
367 * @return void
368 */
369 protected function after_validate(): void {
370 $provider_label = self::TURNSTILE === $this->provider ? __( 'Turnstile', 'defender-security' ) : __( 'reCAPTCHA', 'defender-security' );
371 // Case with multi errors.
372 if ( $this->is_unchecked_woo_locations() && $this->is_unchecked_buddypress_locations() ) {
373 // The text of the notation is only in the first key, but we add the number of keys depending on the disabled locations of the plugins.
374 /* translators: Provider label. */
375 $message = esc_html__(
376 'You have enabled %s for more than one plugin. Please select at least one form location for each plugin and click Save Changes again.',
377 'defender-security'
378 );
379
380 $this->errors['enable_woo'] = sprintf( $message, $provider_label );
381 $this->errors['enable_buddypress'] = '';
382 } else {
383 // Individual cases with plugins.
384 if ( $this->is_unchecked_woo_locations() ) {
385 /* translators: Provider label. */
386 $message = esc_html__(
387 '%s for WooCommerce is enabled, but no WooCommerce forms are selected. Please select at least one WooCommerce form location and then click Save Changes again.',
388 'defender-security'
389 );
390
391 $this->errors['enable_woo'] = sprintf( $message, $provider_label );
392 }
393 if ( $this->is_unchecked_buddypress_locations() ) {
394 /* translators: Provider label. */
395 $message = esc_html__(
396 '%s for BuddyPress is enabled, but no BuddyPress forms are selected. Please select at least one BuddyPress form location and then click Save Changes again.',
397 'defender-security'
398 );
399
400 $this->errors['enable_buddypress'] = sprintf( $message, $provider_label );
401 }
402 }
403 }
404
405 /**
406 * Disable for logged in users or enable.
407 *
408 * @return bool
409 */
410 public function display_for_known_users(): bool {
411 if ( self::TURNSTILE === $this->provider ) {
412 return true;
413 }
414 return ! ( $this->disable_for_known_users && is_user_logged_in() );
415 }
416
417 /**
418 * Get module name.
419 *
420 * @return string
421 */
422 public static function get_module_name(): string {
423 return esc_html__( 'CAPTCHA', 'defender-security' );
424 }
425
426 /**
427 * Get active captcha data based on provider and active type.
428 *
429 * @param string|null $provider The provider name.
430 * @param string|null $active_type The active type.
431 *
432 * @return array The captcha data for the specified provider.
433 */
434 public function get_active_captcha_data( ?string $provider = null, ?string $active_type = null ): array {
435 $provider = $provider ?? $this->provider;
436 $active_type = $active_type ?? $this->active_type;
437 if ( self::TURNSTILE === $provider ) {
438 return $this->data_turnstile;
439 }
440 return match ( $active_type ) {
441 'v2_checkbox' => $this->data_v2_checkbox,
442 'v2_invisible' => $this->data_v2_invisible,
443 'v3_recaptcha' => $this->data_v3_recaptcha,
444 default => array(),
445 };
446 }
447 }
448