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

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

549 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 string $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 string $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 */
373 function defender_no_fresh_install() {
374 if ( empty( get_site_option( 'wd_nofresh_install' ) ) ) {
375 update_site_option( 'wd_nofresh_install', true );
376 }
377 }
378
379 /**
380 * Polyfill for PHP version < 7.3.
381 */
382 if ( ! function_exists( 'array_key_first' ) ) {
383 function array_key_first( array $arr ) {
384 $arr_keys = array_keys( $arr );
385
386 return $arr_keys[0] ?? null;
387 }
388 }
389
390 /**
391 * Fetch request url.
392 *
393 * @return string
394 */
395 function defender_get_request_url() {
396 return home_url( esc_url( filter_input( INPUT_SERVER, 'REQUEST_URI' ) ) );
397 }
398
399 /**
400 * Check that current page is from Defender.
401 *
402 * @return bool
403 */
404 function defender_current_page() {
405 $pages = array(
406 'wp-defender',
407 'wdf-hardener',
408 'wdf-scan',
409 'wdf-logging',
410 'wdf-ip-lockout',
411 'wdf-waf',
412 'wdf-2fa',
413 'wdf-advanced-tools',
414 'wdf-notification',
415 'wdf-setting',
416 'wdf-tutorial',
417 );
418 $page = $_GET['page'] ?? null;
419
420 return in_array( $page, $pages, true );
421 }
422
423 /**
424 * Return the high contrast css class if it is.
425 *
426 * @since 2.7.0
427 * @return bool
428 */
429 function defender_high_contrast() {
430 $model = new \WP_Defender\Model\Setting\Main_Setting();
431
432 return $model->high_contrast_mode;
433 }
434
435 /**
436 * Add more cron schedules for plugin modules. E.g. schedules:
437 * cleaning completed Scan logs,
438 * cleaning temporary firewall IPs,
439 * send reports,
440 * update MaxMind DB.
441 * @since 2.7.1
442 *
443 * @param array $schedules
444 *
445 * @return array
446 */
447 function defender_cron_schedules( $schedules ) {
448 if ( ! isset( $schedules['thirty_minutes'] ) ) {
449 $schedules['thirty_minutes'] = array(
450 'interval' => 30 * MINUTE_IN_SECONDS,
451 'display' => __( 'Every Half Hour', 'wpdef' ),
452 );
453 }
454 if ( ! isset( $schedules['weekly'] ) ) {
455 $schedules['weekly'] = array(
456 'interval' => WEEK_IN_SECONDS,
457 'display' => __( 'Weekly', 'wpdef' ),
458 );
459 }
460 if ( ! isset( $schedules['monthly'] ) ) {
461 $schedules['monthly'] = array(
462 'interval' => MONTH_IN_SECONDS,
463 'display' => __( 'Once Monthly', 'wpdef' ),
464 );
465 }
466 // Todo: find the right solution because 'monthly' (from Firewall)='thirty_days' (from Security_Key tweak).
467 // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year.
468 if ( ! isset( $schedules['thirty_days'] ) ) {
469 $schedules['thirty_days'] = array(
470 'interval' => 2592000,
471 'display' => __( '30 days', 'wpdef' ),
472 );
473 }
474 if ( ! isset( $schedules['sixty_days'] ) ) {
475 $schedules['sixty_days'] = array(
476 'interval' => 5184000,
477 'display' => __( '60 days', 'wpdef' ),
478 );
479 }
480 if ( ! isset( $schedules['ninety_days'] ) ) {
481 $schedules['ninety_days'] = array(
482 'interval' => 7776000,
483 'display' => __( '90 days', 'wpdef' ),
484 );
485 }
486 if ( ! isset( $schedules['six_months'] ) ) {
487 $schedules['six_months'] = array(
488 'interval' => 15780000,
489 'display' => __( '6 months', 'wpdef' ),
490 );
491 }
492 if ( ! isset( $schedules['one_year'] ) ) {
493 $schedules['one_year'] = array(
494 'interval' => 31536000,
495 'display' => __( '1 year', 'wpdef' ),
496 );
497 }
498
499 return $schedules;
500 }
501
502 /**
503 * Generate random string.
504 *
505 * @param int $length Length of random string.
506 * @param string $strings Characters to include in a random string.
507 *
508 * @since 3.0.0
509 * @return string
510 */
511 function defender_generate_random_string( $length = 16, $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) {
512 if ( defined( 'DEFENDER_2FA_SECRET' ) ) {
513 // Only use in test.
514 return constant( 'DEFENDER_2FA_SECRET' );
515 }
516
517 if ( ! is_string( $strings ) ) {
518 return '';
519 }
520
521 $secret = array();
522 for ( $i = 0; $i < $length; $i ++ ) {
523 $secret[] = $strings[ random_int( 0, strlen( $strings ) - 1 ) ];
524 }
525
526 return implode( '', $secret );
527 }
528
529 /**
530 * Either return array or echo json.
531 *
532 * @param mixed $data A Data to be returned or echoed.
533 * @param bool $success Is it a success or failure.
534 * @param bool $return True if data needs to be returned.
535 *
536 * @since 3.0.0
537 * @return array|void
538 */
539 function defender_maybe_echo_json( $data, $success, $return ) {
540 if ( true === $return ) {
541 return array(
542 'success' => $success,
543 'data' => $data,
544 );
545 } else {
546 $success ? wp_send_json_success( $data ) : wp_send_json_error( $data );
547 }
548 }
549