PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.2
Defender Security – Malware Scanner, Login Security & Firewall v6.2.2
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 / component / captcha / class-provider.php

class-provider.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.2, at src/component/captcha/class-provider.php

387 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base provider for captcha integrations.
4 *
5 * @package WP_Defender\Component\Captcha
6 */
7
8 namespace WP_Defender\Component\Captcha;
9
10 use WP_Defender\Traits\IP;
11 use WP_Defender\Component\Crypt;
12 use WP_Defender\Model\Setting\Captcha;
13
14 /**
15 * Abstract captcha provider.
16 */
17 abstract class Provider implements Interface_Provider {
18
19 use IP;
20
21 /**
22 * Captcha settings model.
23 *
24 * @var Captcha
25 */
26 protected Captcha $model;
27
28 /**
29 * Score threshold for v3.
30 *
31 * @var float
32 */
33 protected float $threshold = 0.1;
34
35 /**
36 * Widget language.
37 *
38 * @var string
39 */
40 protected string $language = 'auto';
41
42 /**
43 * Widget size.
44 *
45 * @var string
46 */
47 protected string $size = 'normal';
48
49 /**
50 * Widget theme.
51 *
52 * @var string
53 */
54 protected string $theme = '';
55
56 /**
57 * Secret key.
58 *
59 * @var string
60 */
61 protected string $secret = '';
62
63 /**
64 * Site key.
65 *
66 * @var string
67 */
68 protected string $key = '';
69
70 /**
71 * Captcha type.
72 *
73 * @var string
74 */
75 public string $type = '';
76
77 /**
78 * Error message.
79 *
80 * @var string
81 */
82 protected string $message = '';
83
84 /**
85 * Provider constructor.
86 *
87 * @param Captcha $model Captcha settings model.
88 */
89 public function __construct( Captcha $model ) {
90 $this->model = $model;
91 $this->type = $this->model->active_type;
92 $data = $this->get_data();
93 $this->key = $data['key'];
94 $this->secret = $data['secret'];
95 $this->theme = $data['style'];
96 $this->size = $data['size'];
97 $this->message = $data['message'];
98 if ( isset( $data['threshold'] ) ) {
99 $this->threshold = (float) $data['threshold'];
100 }
101 if ( 'turnstile' === $this->model->provider ) {
102 $this->language = $this->model->data_turnstile['language'] ?? 'auto';
103 } else {
104 $this->language = $this->model->language;
105 }
106 }
107
108 /**
109 * Get provider settings from model.
110 *
111 * @return array
112 */
113 abstract protected function get_data(): array;
114
115 /**
116 * Get the response token from request.
117 *
118 * @return string
119 */
120 public function get_response(): string {
121 return stripslashes( defender_get_data_from_request( $this->get_response_field(), 'p' ) );
122 }
123
124 /**
125 * Get the request field name for responses.
126 *
127 * @return string
128 */
129 abstract protected function get_response_field(): string;
130
131 /**
132 * Get verification URL.
133 *
134 * @return string
135 */
136 abstract public function get_verify_url(): string;
137
138 /**
139 * Get formatted error message for display.
140 *
141 * @return string
142 */
143 public function error_message(): string {
144 return sprintf(
145 /* translators: 1: Opening strong tag, 2: Closing strong tag, 3: Error message */
146 esc_html__( '%1$sError:%2$s %3$s', 'defender-security' ),
147 '<strong>',
148 '</strong>',
149 esc_html( $this->message )
150 );
151 }
152
153 /**
154 * Get duplicate captcha warning message.
155 *
156 * @return string
157 */
158 public function get_duplicate_captcha_warning(): string {
159 return sprintf(
160 /* translators: 1: Opening strong tag, 2: Closing strong tag, 3: Captcha type name */
161 esc_html__( '%1$sWarning:%2$s&nbsp; More than one %3$s has been found in the current form. Please remove all unnecessary %3$s fields to make it work properly.', 'defender-security' ),
162 '<strong>',
163 '</strong>',
164 esc_html( $this->get_name() )
165 );
166 }
167
168 /**
169 * Render the provider widget HTML.
170 *
171 * @return string HTML markup for the captcha widget.
172 */
173 public function render_widget(): string {
174 // Generate random id to prevent duplicate IDs when multiple widgets are rendered.
175 $id = Crypt::random_int( 0, mt_getrandmax() );
176 $content = sprintf( '<div class="captcha_wrap wpdef_captcha_%s">', esc_attr( $this->type ) );
177
178 if ( '' === $this->key || '' === $this->secret ) {
179 $content .= '</div>';
180 return $content;
181 }
182
183 $content .= $this->get_widget_content( $id );
184 $content .= '</div>';
185
186 return $content;
187 }
188
189 /**
190 * Get provider widget content.
191 *
192 * @param int $id Unique widget id.
193 *
194 * @return string
195 */
196 abstract protected function get_widget_content( int $id ): string;
197
198 /**
199 * Build admin notice data for the captcha settings UI.
200 *
201 * @param bool $is_woo_activated Whether WooCommerce is active.
202 * @param bool $is_buddypress_activated Whether BuddyPress is active.
203 *
204 * @return array
205 */
206 public function get_notice_data( bool $is_woo_activated, bool $is_buddypress_activated ): array {
207 $is_active = $this->model->is_active();
208 $notice_type = 'warning';
209 $notice_text = false;
210
211 if ( $is_active ) {
212 $has_location = $this->model->enable_default_location()
213 || $this->model->check_woo_locations( $is_woo_activated )
214 || $this->model->check_buddypress_locations( $is_buddypress_activated );
215 if ( $has_location ) {
216 $notice_type = 'success';
217 $notice_text = false;
218 } elseif ( ! $is_woo_activated && ! $is_buddypress_activated && ! $this->model->enable_default_location() ) {
219 $notice_type = 'warning';
220 $notice_text = sprintf(
221 /* translators: 1: Captcha Provider name, 2: Captcha Type */
222 esc_html__( '%1$s is currently inactive for all forms. You can deploy %2$s for specific forms in the CAPTCHA Locations below.', 'defender-security' ),
223 $this->get_name(),
224 $this->get_name( true )
225 );
226 } elseif ( ! $this->model->enable_default_location() && ( ( $is_woo_activated && ! $this->model->enable_woo_location() ) || ( $is_buddypress_activated && ! $this->model->enable_buddypress_location() ) ) ) {
227 $notice_type = 'warning';
228 $notice_text = sprintf(
229 /* translators: 1: Captcha Provider name, 2: Opening bold tag, 3: Closing bold tag */
230 esc_html__( '%1$s is currently inactive for all forms. You can deploy %1$s for specific forms in the %2$sCAPTCHA Locations%3$s, %2$sWooCommerce%3$s or %2$sBuddyPress%3$s settings below.', 'defender-security' ),
231 $this->get_name(),
232 '<b>',
233 '</b>'
234 );
235 }
236 } else {
237 // Inactive case.
238 $notice_type = 'error';
239 $notice_text = esc_html__( 'Configure a CAPTCHA provider to verify users and prevent automated bots. Both reCAPTCHA and Cloudflare Turnstile are supported.', 'defender-security' );
240 }
241
242 return array(
243 'notice_type' => $notice_type,
244 'notice_text' => $notice_text,
245 );
246 }
247
248 /**
249 * Get primary user IP address.
250 *
251 * @return string
252 */
253 protected function get_primary_ip(): string {
254 $ips = $this->get_user_ip();
255
256 /**
257 * Filters the remote IP address for captcha verification.
258 *
259 * @param string $user_ip The remote IP address.
260 * @param string $provider_type Captcha provider type.
261 *
262 * @since 5.9.0
263 */
264 $ip = apply_filters( 'wd_captcha_remote_ip', $ips[0], $this->type );
265 return is_string( $ip ) ? $ip : (string) $ip;
266 }
267
268 /**
269 * Filter the captcha verification result.
270 *
271 * @param bool $result Verification result.
272 * @param string $form Form identifier.
273 *
274 * @return bool
275 */
276 protected function filter_check_result( bool $result, string $form ): bool {
277 /**
278 * Filters the result of a captcha verification.
279 *
280 * @param bool $result The verification result.
281 * @param string $form The form being verified.
282 * @param string $provider_type Captcha provider type.
283 *
284 * @since 5.9.0
285 */
286 $result = apply_filters( 'wd_captcha_check_result', $result, $form, $this->type );
287 return is_bool( $result ) ? $result : (bool) $result;
288 }
289
290 /**
291 * Filter excluded requests for captcha checks.
292 *
293 * @param array $requests Default excluded requests.
294 *
295 * @return array
296 */
297 protected function filter_excluded_requests( array $requests = array() ): array {
298 /**
299 * Filters the list of excluded request slugs for captcha verification.
300 *
301 * @param array $requests Request slugs to exclude from captcha checks.
302 * @param string $provider_type Captcha provider type.
303 *
304 * @since 5.9.0
305 */
306 $requests = apply_filters( 'wd_captcha_excluded_requests', $requests, $this->type );
307 return is_array( $requests ) ? $requests : (array) $requests;
308 }
309
310 /**
311 * Filter excluded script handles for duplicate captcha detection.
312 *
313 * @param array $handles Default excluded handles.
314 *
315 * @return array
316 */
317 protected function filter_excluded_handles( array $handles = array() ): array {
318 /**
319 * Filters the list of excluded script handles for duplicate captcha detection.
320 *
321 * @param array $handles Script handles to ignore when checking for duplicates.
322 * @param string $provider_type Captcha provider type.
323 *
324 * @since 5.9.0
325 */
326 $handles = apply_filters( 'wd_captcha_excluded_handles', $handles, $this->type );
327 return is_array( $handles ) ? $handles : (array) $handles;
328 }
329
330 /**
331 * Verify a captcha response via remote request.
332 *
333 * @param string $url Endpoint URL.
334 * @param array $body Request body.
335 *
336 * @return array
337 */
338 protected function verify_request( string $url, array $body ): array {
339 $response = wp_remote_post(
340 $url,
341 array(
342 'body' => $body,
343 'sslverify' => true,
344 )
345 );
346 if ( is_wp_error( $response ) ) {
347 return array( 'success' => false );
348 }
349
350 $body = wp_remote_retrieve_body( $response );
351 $response_body = json_decode( $body, true );
352
353 if ( ! is_array( $response_body ) || array() === $response_body ) {
354 return array( 'success' => false );
355 }
356
357 return $response_body;
358 }
359
360 /**
361 * Verify a captcha response token via remote request.
362 *
363 * @param string $token Response token.
364 * @param string $url Endpoint URL.
365 * @param array $body Additional request body data.
366 *
367 * @return array
368 */
369 public function verify_response_token( string $token, string $url, array $body = array() ): array {
370 if ( '' === $token ) {
371 return array( 'success' => false );
372 }
373
374 $body = wp_parse_args(
375 $body,
376 array(
377 'secret' => $this->secret,
378 'response' => $token,
379 )
380 );
381
382 $response_body = $this->verify_request( $url, $body );
383
384 return is_array( $response_body ) ? $response_body : array( 'success' => false );
385 }
386 }
387