PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.1
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 / functions.php

functions.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/functions.php

548 lines 13.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;
4 }
5
6 /**
7 * @param $path
8 *
9 * @return string
10 */
11 function defender_asset_url( $path ) {
12 $base_url = plugin_dir_url( __DIR__ );
13
14 return untrailingslashit( $base_url ) . $path;
15 }
16
17 /**
18 * @param $path
19 *
20 * @return string
21 */
22 function defender_path( $path ) {
23 $base_path = plugin_dir_path( __DIR__ );
24
25 return $base_path . $path;
26 }
27
28 /**
29 * Sanitize submitted data.
30 *
31 * @param array $data
32 *
33 * @return array
34 */
35 function defender_sanitize_data( $data ) {
36 foreach ( $data as $key => &$value ) {
37 if ( is_array( $value ) ) {
38 $value = defender_sanitize_data( $value );
39 } else {
40 $value = sanitize_textarea_field( $value );
41 }
42 }
43
44 return $data;
45 }
46
47 /**
48 * Retrieve wp-config.php file path.
49 *
50 * @return string
51 */
52 function defender_wp_config_path() {
53 if ( file_exists( ABSPATH . 'wp-config.php' ) ) {
54 return ABSPATH . 'wp-config.php';
55 }
56
57 if (
58 @file_exists( dirname( ABSPATH ) . '/wp-config.php' )
59 && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' )
60 ) {
61 return dirname( ABSPATH ) . '/wp-config.php';
62 }
63
64 if ( defined( 'WD_TEST' ) && WD_TEST ) {
65 return '/tmp/wordpress-tests-lib/wp-tests-config.php';
66 }
67 }
68
69 /**
70 * Check whether we're on Windows platform or not.
71 *
72 * @return bool
73 */
74 function defender_is_windows() {
75 return '\\' === DIRECTORY_SEPARATOR;
76 }
77
78 /**
79 * @return \DI\Container
80 */
81 function wd_di() {
82 global $wp_defender_di;
83
84 return $wp_defender_di;
85 }
86
87 /**
88 * @return \WP_Defender\Central
89 */
90 function wd_central() {
91 global $wp_defender_central;
92
93 return $wp_defender_central;
94 }
95
96 /**
97 * @since 2.8.0
98 * @return string
99 */
100 function defender_base_action() {
101 return 'wp_defender/v1/hub/';
102 }
103
104 /**
105 * Get backward compatibility. Forminator uses this method.
106 *
107 * @return array
108 */
109 function defender_backward_compatibility() {
110 $wpmu_dev = new \WP_Defender\Behavior\WPMUDEV();
111 $two_fa_settings = new \WP_Defender\Model\Setting\Two_Fa();
112 $controller = wd_di()->get( \WP_Defender\Controller\Two_Factor::class );
113 $list = $controller->dump_routes_and_nonces();
114 $lost_url = add_query_arg(
115 array(
116 'action' => defender_base_action(),
117 '_def_nonce' => $list['nonces']['send_backup_code'],
118 'route' => $controller->check_route( $list['routes']['send_backup_code'] ),
119 ),
120 admin_url( 'admin-ajax.php' )
121 );
122
123 return array(
124 'is_free' => ! $wpmu_dev->is_pro(),
125 'plugin_url' => defender_asset_url( '' ),
126 'two_fa_settings' => $two_fa_settings,
127 'two_fa_component' => \WP_Defender\Component\Two_Fa::class,
128 'lost_url' => $lost_url,
129 );
130 }
131
132 /**
133 * Polyfill functions for supporting WordPress 5.3.
134 *
135 * @since 2.4.2
136 */
137 if ( ! function_exists( 'wp_timezone_string' ) ) {
138 /**
139 * Retrieves the timezone from site settings as a string.
140 *
141 * Uses the `timezone_string` option to get a proper timezone if available,
142 * otherwise falls back to an offset.
143 *
144 * @since 5.3.0
145 *
146 * @return string PHP timezone string or a ±HH:MM offset.
147 */
148 function wp_timezone_string() {
149 $timezone_string = get_option( 'timezone_string' );
150
151 if ( $timezone_string ) {
152 return $timezone_string;
153 }
154
155 $offset = (float) get_option( 'gmt_offset' );
156 $hours = (int) $offset;
157 $minutes = ( $offset - $hours );
158
159 $sign = ( $offset < 0 ) ? '-' : '+';
160 $abs_hour = abs( $hours );
161 $abs_mins = abs( $minutes * 60 );
162 $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
163
164 return $tz_offset;
165 }
166 }
167
168 if ( ! function_exists( 'wp_timezone' ) ) {
169 /**
170 * Retrieves the timezone from site settings as a `DateTimeZone` object.
171 *
172 * Timezone can be based on a PHP timezone string or a ±HH:MM offset.
173 *
174 * @since 5.3.0
175 *
176 * @return DateTimeZone Timezone object.
177 */
178 function wp_timezone() {
179 return new DateTimeZone( wp_timezone_string() );
180 }
181 }
182
183 if ( ! function_exists( 'defender_wp_check_php_version' ) ) {
184 /**
185 * Polyfill for function of WP core wp_check_php_version().
186 *
187 * @since WP 5.1.0
188 * @since WP 5.1.1 Added the {@see 'wp_is_php_version_acceptable'} filter.
189 *
190 * @return array|false Array of PHP version data. False on failure.
191 */
192 function defender_wp_check_php_version() {
193 $version = phpversion();
194 $key = md5( $version );
195
196 $response = get_site_transient( 'php_check_' . $key );
197 if ( false === $response ) {
198 $url = 'http://api.wordpress.org/core/serve-happy/1.0/';
199 if ( wp_http_supports( array( 'ssl' ) ) ) {
200 $url = set_url_scheme( $url, 'https' );
201 }
202
203 $url = add_query_arg( 'php_version', $version, $url );
204
205 $response = wp_remote_get( $url );
206
207 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
208 return false;
209 }
210
211 /**
212 * Response should be an array with:
213 * 'recommended_version' - string - The PHP version recommended by WordPress.
214 * 'is_supported' - boolean - Whether the PHP version is actively supported.
215 * 'is_secure' - boolean - Whether the PHP version receives security updates.
216 * 'is_acceptable' - boolean - Whether the PHP version is still acceptable for WordPress.
217 */
218 $response = json_decode( wp_remote_retrieve_body( $response ), true );
219
220 if ( ! is_array( $response ) ) {
221 return false;
222 }
223
224 set_site_transient( 'php_check_' . $key, $response, WEEK_IN_SECONDS );
225 }
226
227 if ( isset( $response['is_acceptable'] ) && $response['is_acceptable'] ) {
228 /**
229 * Filters whether the active PHP version is considered acceptable by WordPress.
230 *
231 * Returning false will trigger a PHP version warning to show up in the admin dashboard to administrators.
232 *
233 * This filter is only run if the wordpress.org Serve Happy API considers the PHP version acceptable, ensuring
234 * that this filter can only make this check stricter, but not loosen it.
235 *
236 * @since 5.1.1
237 *
238 * @param bool $is_acceptable Whether the PHP version is considered acceptable. Default true.
239 * @param string $version PHP version checked.
240 */
241 $response['is_acceptable'] = (bool) apply_filters( 'wp_is_php_version_acceptable', true, $version );
242 }
243
244 return $response;
245 }
246 }
247
248 /**
249 * Get hostname.
250 *
251 * @return string|null
252 */
253 function defender_get_hostname() {
254 $host = parse_url( get_site_url(), PHP_URL_HOST );
255 $host = str_replace( 'www.', '', $host );
256 $host = explode( '.', $host );
257 if ( is_array( $host ) ) {
258 $host = array_shift( $host );
259 } else {
260 $host = null;
261 }
262
263 return $host;
264 }
265
266 if ( ! function_exists( 'sanitize_mask_url' ) ) {
267 /**
268 * Sanitizes the mask login URL allowing uppercase letters,
269 * Replacing whitespace and a few other characters with dashes and
270 * Limits the output to alphanumeric characters, underscore (_) and dash (-).
271 * Whitespace becomes a dash.
272 *
273 * @param string $title The title to be sanitized.
274 * @return string The sanitized title.
275 */
276 function sanitize_mask_url( $title ) {
277 $title = strip_tags( $title );
278 // Preserve escaped octets.
279 $title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title );
280 // Remove percent signs that are not part of an octet.
281 $title = str_replace( '%', '', $title );
282 // Restore octets.
283 $title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title );
284
285 if ( seems_utf8( $title ) ) {
286 $title = utf8_uri_encode( $title, 200 );
287 }
288
289 // Kill entities.
290 $title = preg_replace( '/&.+?;/', '', $title );
291 $title = str_replace( '.', '-', $title );
292
293 $title = preg_replace( '/[^%a-zA-Z0-9 _-]/', '', $title );
294 $title = preg_replace( '/\s+/', '-', $title );
295 $title = preg_replace( '|-+|', '-', $title );
296
297 return $title;
298 }
299 }
300
301 /**
302 * Return the noreply email.
303 *
304 * An utility function which will return common noreply from address.
305 *
306 * @param string $filter_tag Tag name of the filter to override email address.
307 *
308 * @return string Noreply email.
309 */
310 function defender_noreply_email( $filter_tag = '' ) {
311 $host = wp_parse_url( get_site_url(), PHP_URL_HOST );
312
313 if ( 'www.' === substr( $host, 0, 4 ) ) {
314 $host = substr( $host, 4 );
315 }
316
317 $no_reply_email = 'noreply@' . $host;
318
319 if ( strlen( $filter_tag ) > 0 ) {
320 $no_reply_email = apply_filters( $filter_tag, $no_reply_email );
321 }
322
323 return $no_reply_email;
324 }
325
326 /**
327 * Noreply email header.
328 *
329 * Generate noreply email header with HTML UTF-8 support.
330 *
331 * @param string $email Email.
332 *
333 * @return array Returns the email headers.
334 */
335 function defender_noreply_html_header( $email ) {
336 $headers = array(
337 'From: Defender <' . $email . '>',
338 'Content-Type: text/html; charset=UTF-8',
339 );
340
341 return $headers;
342 }
343
344 /**
345 * Get data of the whitelabel feature from WPMUDEV Dashboard:
346 * hide_branding, hide_doc_link, footer_text, hero_image, change_footer.
347 *
348 * @since 2.5.5
349 * @return array
350 */
351 function defender_white_label_status() {
352 /* translators: %s: heart icon */
353 $footer_text = sprintf( __( 'Made with %s by WPMU DEV', 'wpdef' ), '<i class="sui-icon-heart"></i>' );
354 $custom_image = apply_filters( 'wpmudev_branding_hero_image', '' );
355 $whitelabled = apply_filters( 'wpmudev_branding_hide_branding', false );
356
357 return array(
358 'hide_branding' => apply_filters( 'wpmudev_branding_hide_branding', false ),
359 'hide_doc_link' => apply_filters( 'wpmudev_branding_hide_doc_link', false ),
360 'footer_text' => apply_filters( 'wpmudev_branding_footer_text', $footer_text ),
361 'hero_image' => $custom_image,
362 'change_footer' => apply_filters( 'wpmudev_branding_change_footer', false ),
363 'is_unbranded' => empty( $custom_image ) && $whitelabled,
364 'is_rebranded' => ! empty( $custom_image ) && $whitelabled,
365 );
366 }
367
368 /**
369 * Indicate this is not fresh install.
370 *
371 * @since 2.5.5
372 * @return void
373 */
374 function defender_no_fresh_install() {
375 if ( empty( get_site_option( 'wd_nofresh_install' ) ) ) {
376 update_site_option( 'wd_nofresh_install', true );
377 }
378 }
379
380 /**
381 * Polyfill for PHP version < 7.3.
382 */
383 if ( ! function_exists( 'array_key_first' ) ) {
384 function array_key_first( array $arr ) {
385 $arr_keys = array_keys( $arr );
386
387 return $arr_keys[0] ?? null;
388 }
389 }
390
391 /**
392 * Fetch request url.
393 */
394 function defender_get_request_url() {
395 return home_url( esc_url( filter_input( INPUT_SERVER, 'REQUEST_URI' ) ) );
396 }
397
398 /**
399 * Check that current page is from Defender.
400 *
401 * @return bool
402 */
403 function defender_current_page() {
404 $pages = array(
405 'wp-defender',
406 'wdf-hardener',
407 'wdf-scan',
408 'wdf-logging',
409 'wdf-ip-lockout',
410 'wdf-waf',
411 'wdf-2fa',
412 'wdf-advanced-tools',
413 'wdf-notification',
414 'wdf-setting',
415 'wdf-tutorial',
416 );
417 $page = $_GET['page'] ?? null;
418
419 return in_array( $page, $pages, true );
420 }
421
422 /**
423 * Return the high contrast css class if it is.
424 *
425 * @since 2.7.0
426 * @return bool
427 */
428 function defender_high_contrast() {
429 $model = new \WP_Defender\Model\Setting\Main_Setting();
430
431 return $model->high_contrast_mode;
432 }
433
434 /**
435 * Add more cron schedules for plugin modules. E.g. schedules:
436 * cleaning completed Scan logs,
437 * cleaning temporary firewall IPs,
438 * send reports,
439 * update MaxMind DB.
440 * @since 2.7.1
441 *
442 * @param array $schedules
443 *
444 * @return array
445 */
446 function defender_cron_schedules( $schedules ) {
447 if ( ! isset( $schedules['thirty_minutes'] ) ) {
448 $schedules['thirty_minutes'] = array(
449 'interval' => 30 * MINUTE_IN_SECONDS,
450 'display' => __( 'Every Half Hour', 'wpdef' ),
451 );
452 }
453 if ( ! isset( $schedules['weekly'] ) ) {
454 $schedules['weekly'] = array(
455 'interval' => WEEK_IN_SECONDS,
456 'display' => __( 'Weekly', 'wpdef' ),
457 );
458 }
459 if ( ! isset( $schedules['monthly'] ) ) {
460 $schedules['monthly'] = array(
461 'interval' => MONTH_IN_SECONDS,
462 'display' => __( 'Once Monthly', 'wpdef' ),
463 );
464 }
465 // Todo: find the right solution because 'monthly' (from Firewall)='thirty_days' (from Security_Key tweak).
466 // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year.
467 if ( ! isset( $schedules['thirty_days'] ) ) {
468 $schedules['thirty_days'] = array(
469 'interval' => 2592000,
470 'display' => __( '30 days', 'wpdef' ),
471 );
472 }
473 if ( ! isset( $schedules['sixty_days'] ) ) {
474 $schedules['sixty_days'] = array(
475 'interval' => 5184000,
476 'display' => __( '60 days', 'wpdef' ),
477 );
478 }
479 if ( ! isset( $schedules['ninety_days'] ) ) {
480 $schedules['ninety_days'] = array(
481 'interval' => 7776000,
482 'display' => __( '90 days', 'wpdef' ),
483 );
484 }
485 if ( ! isset( $schedules['six_months'] ) ) {
486 $schedules['six_months'] = array(
487 'interval' => 15780000,
488 'display' => __( '6 months', 'wpdef' ),
489 );
490 }
491 if ( ! isset( $schedules['one_year'] ) ) {
492 $schedules['one_year'] = array(
493 'interval' => 31536000,
494 'display' => __( '1 year', 'wpdef' ),
495 );
496 }
497
498 return $schedules;
499 }
500
501 /**
502 * Generate random string.
503 *
504 * @param int $length Length of random string.
505 * @param string $strings Characters to include in a random string.
506 *
507 * @since 3.0.0
508 * @return string
509 */
510 function defender_generate_random_string( $length = 16, $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) {
511 if ( defined( 'DEFENDER_2FA_SECRET' ) ) {
512 // Only use in test.
513 return constant( 'DEFENDER_2FA_SECRET' );
514 }
515
516 if ( ! is_string( $strings ) ) {
517 return '';
518 }
519
520 $secret = array();
521 for ( $i = 0; $i < $length; $i ++ ) {
522 $secret[] = $strings[ random_int( 0, strlen( $strings ) - 1 ) ];
523 }
524
525 return implode( '', $secret );
526 }
527
528 /**
529 * Either return array or echo json.
530 *
531 * @param mixed $data A Data to be returned or echoed.
532 * @param bool $success Is it a success or failure.
533 * @param bool $return True if data needs to be returned.
534 *
535 * @since 3.0.0
536 * @return array|void
537 */
538 function defender_maybe_echo_json( $data, $success, $return ) {
539 if ( true === $return ) {
540 return array(
541 'success' => $success,
542 'data' => $data,
543 );
544 } else {
545 $success ? wp_send_json_success( $data ) : wp_send_json_error( $data );
546 }
547 }
548