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 / component / captcha / class-provider.php

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

383 lines 9.6 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 $this->language = $this->model->language;
102 }
103
104 /**
105 * Get provider settings from model.
106 *
107 * @return array
108 */
109 abstract protected function get_data(): array;
110
111 /**
112 * Get the response token from request.
113 *
114 * @return string
115 */
116 public function get_response(): string {
117 return stripslashes( defender_get_data_from_request( $this->get_response_field(), 'p' ) );
118 }
119
120 /**
121 * Get the request field name for responses.
122 *
123 * @return string
124 */
125 abstract protected function get_response_field(): string;
126
127 /**
128 * Get verification URL.
129 *
130 * @return string
131 */
132 abstract public function get_verify_url(): string;
133
134 /**
135 * Get formatted error message for display.
136 *
137 * @return string
138 */
139 public function error_message(): string {
140 return sprintf(
141 /* translators: 1: Opening strong tag, 2: Closing strong tag, 3: Error message */
142 esc_html__( '%1$sError:%2$s %3$s', 'defender-security' ),
143 '<strong>',
144 '</strong>',
145 esc_html( $this->message )
146 );
147 }
148
149 /**
150 * Get duplicate captcha warning message.
151 *
152 * @return string
153 */
154 public function get_duplicate_captcha_warning(): string {
155 return sprintf(
156 /* translators: 1: Opening strong tag, 2: Closing strong tag, 3: Captcha type name */
157 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' ),
158 '<strong>',
159 '</strong>',
160 esc_html( $this->get_name() )
161 );
162 }
163
164 /**
165 * Render the provider widget HTML.
166 *
167 * @return string HTML markup for the captcha widget.
168 */
169 public function render_widget(): string {
170 // Generate random id to prevent duplicate IDs when multiple widgets are rendered.
171 $id = Crypt::random_int( 0, mt_getrandmax() );
172 $content = sprintf( '<div class="captcha_wrap wpdef_captcha_%s">', esc_attr( $this->type ) );
173
174 if ( '' === $this->key || '' === $this->secret ) {
175 $content .= '</div>';
176 return $content;
177 }
178
179 $content .= $this->get_widget_content( $id );
180 $content .= '</div>';
181
182 return $content;
183 }
184
185 /**
186 * Get provider widget content.
187 *
188 * @param int $id Unique widget id.
189 *
190 * @return string
191 */
192 abstract protected function get_widget_content( int $id ): string;
193
194 /**
195 * Build admin notice data for the captcha settings UI.
196 *
197 * @param bool $is_woo_activated Whether WooCommerce is active.
198 * @param bool $is_buddypress_activated Whether BuddyPress is active.
199 *
200 * @return array
201 */
202 public function get_notice_data( bool $is_woo_activated, bool $is_buddypress_activated ): array {
203 $is_active = $this->model->is_active();
204 $notice_type = 'warning';
205 $notice_text = false;
206
207 if ( $is_active ) {
208 $has_location = $this->model->enable_default_location()
209 || $this->model->check_woo_locations( $is_woo_activated )
210 || $this->model->check_buddypress_locations( $is_buddypress_activated );
211 if ( $has_location ) {
212 $notice_type = 'success';
213 $notice_text = false;
214 } elseif ( ! $is_woo_activated && ! $is_buddypress_activated && ! $this->model->enable_default_location() ) {
215 $notice_type = 'warning';
216 $notice_text = sprintf(
217 /* translators: 1: Captcha Provider name, 2: Captcha Type */
218 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' ),
219 $this->get_name(),
220 $this->get_name( true )
221 );
222 } elseif ( ! $this->model->enable_default_location() && ( ( $is_woo_activated && ! $this->model->enable_woo_location() ) || ( $is_buddypress_activated && ! $this->model->enable_buddypress_location() ) ) ) {
223 $notice_type = 'warning';
224 $notice_text = sprintf(
225 /* translators: 1: Captcha Provider name, 2: Opening bold tag, 3: Closing bold tag */
226 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' ),
227 $this->get_name(),
228 '<b>',
229 '</b>'
230 );
231 }
232 } else {
233 // Inactive case.
234 $notice_type = 'error';
235 $notice_text = esc_html__( 'Configure a CAPTCHA provider to verify users and prevent automated bots. Both reCAPTCHA and Cloudflare Turnstile are supported.', 'defender-security' );
236 }
237
238 return array(
239 'notice_type' => $notice_type,
240 'notice_text' => $notice_text,
241 );
242 }
243
244 /**
245 * Get primary user IP address.
246 *
247 * @return string
248 */
249 protected function get_primary_ip(): string {
250 $ips = $this->get_user_ip();
251
252 /**
253 * Filters the remote IP address for captcha verification.
254 *
255 * @param string $user_ip The remote IP address.
256 * @param string $provider_type Captcha provider type.
257 *
258 * @since 5.9.0
259 */
260 $ip = apply_filters( 'wd_captcha_remote_ip', $ips[0], $this->type );
261 return is_string( $ip ) ? $ip : (string) $ip;
262 }
263
264 /**
265 * Filter the captcha verification result.
266 *
267 * @param bool $result Verification result.
268 * @param string $form Form identifier.
269 *
270 * @return bool
271 */
272 protected function filter_check_result( bool $result, string $form ): bool {
273 /**
274 * Filters the result of a captcha verification.
275 *
276 * @param bool $result The verification result.
277 * @param string $form The form being verified.
278 * @param string $provider_type Captcha provider type.
279 *
280 * @since 5.9.0
281 */
282 $result = apply_filters( 'wd_captcha_check_result', $result, $form, $this->type );
283 return is_bool( $result ) ? $result : (bool) $result;
284 }
285
286 /**
287 * Filter excluded requests for captcha checks.
288 *
289 * @param array $requests Default excluded requests.
290 *
291 * @return array
292 */
293 protected function filter_excluded_requests( array $requests = array() ): array {
294 /**
295 * Filters the list of excluded request slugs for captcha verification.
296 *
297 * @param array $requests Request slugs to exclude from captcha checks.
298 * @param string $provider_type Captcha provider type.
299 *
300 * @since 5.9.0
301 */
302 $requests = apply_filters( 'wd_captcha_excluded_requests', $requests, $this->type );
303 return is_array( $requests ) ? $requests : (array) $requests;
304 }
305
306 /**
307 * Filter excluded script handles for duplicate captcha detection.
308 *
309 * @param array $handles Default excluded handles.
310 *
311 * @return array
312 */
313 protected function filter_excluded_handles( array $handles = array() ): array {
314 /**
315 * Filters the list of excluded script handles for duplicate captcha detection.
316 *
317 * @param array $handles Script handles to ignore when checking for duplicates.
318 * @param string $provider_type Captcha provider type.
319 *
320 * @since 5.9.0
321 */
322 $handles = apply_filters( 'wd_captcha_excluded_handles', $handles, $this->type );
323 return is_array( $handles ) ? $handles : (array) $handles;
324 }
325
326 /**
327 * Verify a captcha response via remote request.
328 *
329 * @param string $url Endpoint URL.
330 * @param array $body Request body.
331 *
332 * @return array
333 */
334 protected function verify_request( string $url, array $body ): array {
335 $response = wp_remote_post(
336 $url,
337 array(
338 'body' => $body,
339 'sslverify' => false,
340 )
341 );
342 if ( is_wp_error( $response ) ) {
343 return array( 'success' => false );
344 }
345
346 $body = wp_remote_retrieve_body( $response );
347 $response_body = json_decode( $body, true );
348
349 if ( ! is_array( $response_body ) || array() === $response_body ) {
350 return array( 'success' => false );
351 }
352
353 return $response_body;
354 }
355
356 /**
357 * Verify a captcha response token via remote request.
358 *
359 * @param string $token Response token.
360 * @param string $url Endpoint URL.
361 * @param array $body Additional request body data.
362 *
363 * @return array
364 */
365 public function verify_response_token( string $token, string $url, array $body = array() ): array {
366 if ( '' === $token ) {
367 return array( 'success' => false );
368 }
369
370 $body = wp_parse_args(
371 $body,
372 array(
373 'secret' => $this->secret,
374 'response' => $token,
375 )
376 );
377
378 $response_body = $this->verify_request( $url, $body );
379
380 return is_array( $response_body ) ? $response_body : array( 'success' => false );
381 }
382 }
383