PluginProbe ʕ •ᴥ•ʔ
Limit Login Attempts Security – Login Security, 2FA, Firewall, Brute Force Prevention / 3.2.4
Limit Login Attempts Security – Login Security, 2FA, Firewall, Brute Force Prevention v3.2.4
3.2.4 3.2.3 3.2.2 3.2.1 3.2.0 trunk 2.0.0 2.1.0 2.10.0 2.10.1 2.11.0 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.14.0 2.15.0 2.15.1 2.15.2 2.16.0 2.17.0 2.17.1 2.17.2 2.17.3 2.17.4 2.18.0 2.19.0 2.19.1 2.19.2 2.2.0 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.21.1 2.22.0 2.22.1 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.25.0 2.25.1 2.25.10 2.25.11 2.25.12 2.25.13 2.25.14 2.25.15 2.25.16 2.25.17 2.25.18 2.25.19 2.25.2 2.25.20 2.25.21 2.25.22 2.25.23 2.25.24 2.25.25 2.25.26 2.25.27 2.25.28 2.25.29 2.25.3 2.25.4 2.25.5 2.25.6 2.25.7 2.25.8 2.25.9 2.26.0 2.26.1 2.26.10 2.26.11 2.26.12 2.26.13 2.26.14 2.26.15 2.26.16 2.26.17 2.26.18 2.26.19 2.26.2 2.26.20 2.26.21 2.26.22 2.26.23 2.26.24 2.26.25 2.26.26 2.26.27 2.26.28 2.26.3 2.26.4 2.26.5 2.26.6 2.26.7 2.26.8 2.26.9 2.3.0 2.4.0 2.5.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.8.0 2.8.1 2.9.0 3.0.0 3.0.1 3.0.2 3.1.0
limit-login-attempts-reloaded / core / Helpers.php
limit-login-attempts-reloaded / core Last commit date
http 2 weeks ago integrations 2 weeks ago mfa 2 weeks ago mfa-flow 2 weeks ago AdminNoticesController.php 2 weeks ago Ajax.php 2 weeks ago CloudApp.php 2 weeks ago Config.php 2 weeks ago Helpers.php 2 weeks ago LimitLoginAttempts.php 2 weeks ago LoginFlowTransientStore.php 2 weeks ago MfaConstants.php 2 weeks ago Shortcodes.php 2 weeks ago
Helpers.php
584 lines
1 <?php
2
3 namespace LLAR\Core;
4
5 use LLAR\Lib\CidrCheck;
6
7 if ( !defined( 'ABSPATH' ) ) exit;
8
9 class Helpers {
10
11 /**
12 * @param string $msg
13 * @param bool $is_error
14 */
15 public static function show_message( $msg = '', $is_error = false ) {
16 if ( empty( $msg ) ) {
17 return;
18 }
19
20 $class = $is_error ? 'error' : 'updated';
21 echo '<div id="message" class="' . $class . ' fade"><p>' . $msg . '</p></div>';
22 }
23
24 /**
25 * @param $log
26 *
27 * @return array
28 */
29 public static function sorted_log_by_date( $log ) {
30 $new_log = array();
31
32 if ( ! is_array( $log ) || empty( $log ) ) {
33 return $new_log;
34 }
35
36 foreach ( $log as $ip => $users ) {
37
38 if ( ! empty( $users ) ) {
39 foreach ( $users as $user_name => $info ) {
40
41 if ( is_array( $info ) && ! empty( $info['date'] ) && ! empty( $info['counter'] ) ) { // For new plugin version
42 $new_log[ $info['date'] ] = array(
43 'ip' => $ip,
44 'username' => $user_name,
45 'counter' => $info['counter'],
46 'gateway' => isset( $info['gateway'] ) ? $info['gateway'] : '-',
47 'unlocked' => ! empty( $info['unlocked'] ),
48 );
49 continue;
50 }
51
52 if ( ! is_array( $info ) ) { // For old plugin version
53 $new_log[0] = array(
54 'ip' => $ip,
55 'username' => $user_name,
56 'counter' => $info,
57 'gateway' => '-',
58 'unlocked' => false,
59 );
60 }
61 }
62 }
63 }
64
65 krsort( $new_log );
66
67 return $new_log;
68 }
69
70 public static function get_countries_list() {
71
72 if ( ! ( $countries = require LLA_PLUGIN_DIR . '/resources/countries.php' ) ) {
73
74 return array();
75 }
76
77 asort( $countries );
78
79 return $countries;
80 }
81
82 public static function get_continent_list() {
83
84 if ( ! ( $continent = require LLA_PLUGIN_DIR . '/resources/continent.php' ) ) {
85
86 return array();
87 }
88
89 asort( $continent );
90
91 return $continent;
92 }
93
94 /**
95 * @param $ip
96 * @param $cidr
97 *
98 * @return bool
99 */
100 public static function check_ip_cidr( $ip, $cidr ) {
101
102 if ( ! $ip || ! $cidr ) {
103 return false;
104 }
105
106 $cidr_checker = new CidrCheck();
107
108 return $cidr_checker->match( $ip, $cidr );
109 }
110
111 /**
112 * Checks if the plugin is installed as Must Use plugin
113 *
114 * @return bool
115 */
116 public static function is_mu() {
117
118 return ( strpos( LLA_PLUGIN_DIR, 'mu-plugins' ) !== false );
119 }
120
121 /**
122 * @param $content
123 *
124 * @return string|string[]|null
125 */
126 public static function deslash( $content ) {
127
128 $content = preg_replace( "/\\\+'/", "'", $content );
129 $content = preg_replace( '/\\\+"/', '"', $content );
130 $content = preg_replace( '/\\\+/', '\\', $content );
131
132 return $content;
133 }
134
135 // Solution prevents double quotes problem in json string
136 public static function sanitize_stripslashes_deep( $value )
137 {
138 if ( is_array( $value ) ) {
139 return array_map( [self::class, 'sanitize_stripslashes_deep'], $value );
140 }
141
142 if ( is_bool( $value ) || is_null( $value ) ) {
143 return $value;
144 }
145
146 return sanitize_textarea_field( stripslashes( (string)$value ) );
147 }
148
149
150 public static function is_auto_update_enabled() {
151 $auto_update_plugins = get_site_option( 'auto_update_plugins' );
152 return is_array( $auto_update_plugins ) && in_array( LLA_PLUGIN_BASENAME, $auto_update_plugins );
153 }
154
155 public static function is_block_automatic_update_disabled() {
156
157 if ( ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS )
158 || ( defined( 'DOING_CRON' ) && DOING_CRON ) ) {
159 return true;
160 }
161
162 return apply_filters( 'automatic_updater_disabled', false ) || ! apply_filters( 'auto_update_plugin', true, 10, 2 );
163 }
164
165 public static function get_wordpress_version() {
166 global $wp_version;
167 return $wp_version;
168 }
169
170 /**
171 * @return bool
172 */
173 public static function is_network_mode() {
174 if ( !is_multisite() ) return false;
175
176 require_once ABSPATH.'wp-admin/includes/plugin.php';
177
178 return is_plugin_active_for_network( 'limit-login-attempts-reloaded/limit-login-attempts-reloaded.php' );
179 }
180
181 /**
182 * @return bool
183 */
184 public static function allow_local_options() {
185
186 if( !self::is_network_mode() ) return true;
187
188 return get_site_option( 'limit_login_allow_local_options', false );
189 }
190
191 /**
192 * @return bool
193 */
194 public static function use_local_options() {
195
196 if( !self::is_network_mode() ) return true;
197
198 return get_site_option( 'limit_login_allow_local_options', false ) &&
199 get_option( 'limit_login_use_local_options', false );
200 }
201
202 /**
203 * @param $new_app_config
204 * @param false $update_created_at
205 *
206 * @return false
207 */
208 public static function cloud_app_update_config( $new_app_config, $update_created_at = false ) {
209 if( !$new_app_config ) return false;
210
211 if( $active_app_config = Config::get( 'app_config' ) ) {
212
213 foreach ( $active_app_config['settings'] as $key => $info ) {
214
215 if( array_key_exists( $key, $new_app_config['settings'] ) ) {
216
217 if( !empty( $new_app_config['settings'][$key]['options'] ) &&
218 !in_array( $info['value'], $new_app_config['settings'][$key]['options'] ) ) {
219
220 continue;
221 }
222
223 $new_app_config['settings'][$key]['value'] = $info['value'];
224 }
225 }
226
227 }
228
229 if( $update_created_at )
230 $new_app_config['created_at'] = time();
231
232 Config::update( 'app_config', $new_app_config );
233 }
234
235 /**
236 * @param $filepath
237 *
238 * @return bool
239 */
240 public static function is_writable( $filepath ) {
241 return file_exists( $filepath ) && wp_is_writable( $filepath );
242 }
243
244 public static function ip_in_range( $ip, $list ) {
245
246 foreach ( $list as $range ) {
247
248 $range = array_map('trim', explode('-', $range) );
249 if ( count( $range ) == 1 ) {
250
251 // CIDR
252 if( strpos( $range[0], '/' ) !== false && self::check_ip_cidr( $ip, $range[0] ) ) {
253
254 return true;
255 }
256 // Single IP
257 else if ( (string)$ip === (string)$range[0] ) {
258
259 return true;
260 }
261
262 } else {
263
264 $low = ip2long( $range[0] );
265 $high = ip2long( $range[1] );
266 $needle = ip2long( $ip );
267
268 if ( $low === false || $high === false || $needle === false )
269 continue;
270
271 $low = (float)sprintf("%u",$low);
272 $high = (float)sprintf("%u",$high);
273 $needle = (float)sprintf("%u",$needle);
274
275 if ( $needle >= $low && $needle <= $high )
276 return true;
277 }
278 }
279
280 return false;
281 }
282
283 public static function detect_ip_address( $trusted_ip_origins ) {
284 if( empty( $trusted_ip_origins ) || !is_array( $trusted_ip_origins ) ) {
285
286 $trusted_ip_origins = array();
287 }
288
289 if( !in_array( 'REMOTE_ADDR', $trusted_ip_origins ) ) {
290
291 $trusted_ip_origins[] = 'REMOTE_ADDR';
292 }
293
294 $ip = '';
295 foreach ( $trusted_ip_origins as $origin ) {
296
297 if( isset( $_SERVER[$origin] ) && !empty( $_SERVER[$origin] ) ) {
298
299 if( strpos( $_SERVER[$origin], ',' ) !== false ) {
300
301 $origin_ips = explode( ',', $_SERVER[$origin] );
302 $origin_ips = array_map( 'trim', $origin_ips );
303
304 if( $origin_ips ) {
305
306 foreach ($origin_ips as $check_ip) {
307
308 if( self::is_ip_valid( $check_ip ) ) {
309
310 $ip = $check_ip;
311 break 2;
312 }
313 }
314 }
315 }
316
317 if( self::is_ip_valid( $_SERVER[$origin] ) ) {
318
319 $ip = $_SERVER[$origin];
320 break;
321 }
322 }
323 }
324
325 $ip = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ip);
326
327 return $ip;
328 }
329
330 public static function get_all_ips() {
331
332 $ips = array();
333
334 foreach ( $_SERVER as $key => $value ) {
335
336 if( in_array( $key, array( 'SERVER_ADDR' ) ) ) continue;
337
338 // If REMOTE_ADDR contains multiple values (comma-separated), keep only the first before validation
339 if ( $key === 'REMOTE_ADDR' && strpos( $value, ',' ) !== false ) {
340
341 $parts = explode( ',', $value );
342 $value = trim( $parts[0] );
343 }
344
345 if( $valid_ip = self::is_ip_valid( $value ) ) {
346
347 $ips[$key] = $valid_ip;
348 }
349 }
350
351 if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) && !array_key_exists( 'HTTP_X_FORWARDED_FOR', $ips ) ) {
352
353 $ips['HTTP_X_FORWARDED_FOR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
354 }
355
356 return $ips;
357 }
358
359 public static function is_ip_valid( $ip ) {
360 if( empty( $ip ) ) return false;
361
362 return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ?:
363 filter_var( preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ip ), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 );
364 }
365
366 public static function detect_gateway() {
367
368 $gateway = 'wp_login';
369 // Use raw path for matching; avoid sanitize_text_field() which can alter the URI and break gateway detection.
370 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? rawurldecode( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
371 $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';
372
373 // Some plugins hide wp-login.php and mask REQUEST_URI.
374 // Prefer core routing marker when available.
375 if ( isset( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) {
376 switch ( $action ) {
377 case 'lostpassword':
378 return 'wp_lostpassword';
379 case 'register':
380 return 'wp_register';
381 default:
382 return 'wp_login';
383 }
384 }
385
386 switch ( true ) {
387 case false !== strpos( $request_uri, 'wp-login.php' ) && ( ! $action || 'login' === $action ):
388 $gateway = 'wp_login';
389 break;
390 case 'lostpassword' === $action && false !== strpos( $request_uri, 'wp-login.php' ):
391 $gateway = 'wp_lostpassword';
392 break;
393 case 'register' === $action && false !== strpos( $request_uri, 'wp-login.php' ):
394 $gateway = 'wp_register';
395 break;
396 case isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ):
397 $gateway = 'wp_xmlrpc';
398 break;
399 case false === strpos( $request_uri, 'wp-login.php' ):
400 $gateway = trim( $request_uri, '/' );
401 $gateway = str_replace( '/', '_', $gateway );
402 $gateway = substr( sanitize_key( $gateway ), 0, 100 );
403 if ( empty( $gateway ) ) {
404 $gateway = 'custom_login';
405 }
406 break;
407 }
408
409 return $gateway;
410 }
411
412 public static function short_number($num) {
413 $units = array( '', 'K', 'M', 'B', 'T' );
414 for ($i = 0; $num >= 1000; $i++) {
415 $num /= 1000;
416 }
417 return round($num, 1) . $units[$i];
418 }
419
420 public static function send_mail_with_logo( $to, $subject, $body ) {
421
422 add_action( 'phpmailer_init', array( 'LLAR\Core\Helpers', 'add_attachments_to_php_mailer' ) );
423
424 @wp_mail( $to, $subject, $body, array( 'content-type: text/html' ) );
425
426 remove_action( 'phpmailer_init', array( 'LLAR\Core\Helpers', 'add_attachments_to_php_mailer' ) );
427 }
428
429 public static function add_attachments_to_php_mailer( &$phpmailer ) {
430 $logo_path = LLA_PLUGIN_DIR . 'assets/img/logo.png';
431
432 if( file_exists( $logo_path ) ) {
433 $phpmailer->AddEmbeddedImage( $logo_path, 'logo' );
434 }
435 }
436
437 public static function wp_locale() {
438 return str_replace( '_', '-', get_locale() );
439 }
440
441 /**
442 * Obfuscate email for handshake API: first+asterisks+last per part, preserving length (e.g. t**t@*******.***).
443 *
444 * @param string $email Raw email address.
445 * @return string Obfuscated email or empty string if invalid.
446 */
447 public static function obfuscate_email( $email ) {
448 $email = trim( (string) $email );
449 if ( $email === '' ) {
450 return '';
451 }
452 if ( ! preg_match( LLA_EMAIL_OBFUSCATE_REGEX, $email ) ) {
453 return '***@***.***';
454 }
455 $after_local = preg_replace( LLA_EMAIL_OBFUSCATE_LOCAL, '*', $email );
456 $after_domain = preg_replace_callback( '/@(.*)$/', function ( $m ) {
457 return '@' . preg_replace_callback( '/[^.]+/', function ( $m2 ) {
458 return str_repeat( '*', strlen( $m2[0] ) );
459 }, $m[1] );
460 }, $after_local );
461 return $after_domain;
462 }
463
464
465 /**
466 * Retrieves debug information for the Debug tab.
467 *
468 * @return string Debug info as a multi-line string.
469 */
470 public static function get_debug_info() {
471 $debug_info = '';
472 $ips = array();
473 $server = array();
474
475 foreach ( $_SERVER as $key => $value ) {
476 if ( in_array( $key, array( 'SERVER_ADDR' ), true ) || is_array( $value ) ) {
477 continue;
478 }
479 $ips_for_check = array_map( 'trim', explode( ',', $value ) );
480 foreach ( $ips_for_check as $ip ) {
481 if ( self::is_ip_valid( $ip ) ) {
482 if ( ! in_array( $ip, $ips, true ) ) {
483 $ips[] = $ip;
484 }
485 if ( ! isset( $server[ $key ] ) ) {
486 $server[ $key ] = '';
487 }
488 if ( in_array( $ip, array( '127.0.0.1', '0.0.0.0' ), true ) ) {
489 $server[ $key ] = $ip;
490 } else {
491 $server[ $key ] .= 'IP' . array_search( $ip, $ips, true ) . ',';
492 }
493 }
494 }
495 }
496 $debug_info .= 'IPs:' . "\n";
497 foreach ( $server as $server_key => $ips_val ) {
498 $debug_info .= $server_key . ' = ' . trim( $ips_val, ',' ) . "\n";
499 }
500
501 $plugin_data = get_plugin_data( LLA_PLUGIN_FILE );
502 if ( is_array( $plugin_data ) ) {
503 $version = isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : '';
504 $debug_info .= "\nPlugin Version: " . $version . "\n";
505 } else {
506 $debug_info .= "\nPlugin Version: \n";
507 }
508 $debug_info .= 'WordPress Version: ' . get_bloginfo( 'version' ) . "\n";
509 $debug_info .= 'Is Multisite: ' . ( is_multisite() ? 'yes' : 'no' ) . "\n";
510 $debug_info .= "\nActive Plugins:\n";
511 $all_plugins = get_plugins();
512 $active_plugins = get_option( 'active_plugins' );
513 if ( is_array( $active_plugins ) ) {
514 foreach ( $active_plugins as $plugin_file ) {
515 if ( isset( $all_plugins[ $plugin_file ] ) ) {
516 $plugin_data_item = $all_plugins[ $plugin_file ];
517
518 $name = isset( $plugin_data_item['Name'] ) ? $plugin_data_item['Name'] : '';
519 $version = isset( $plugin_data_item['Version'] ) ? $plugin_data_item['Version'] : '';
520 $uri = isset( $plugin_data_item['PluginURI'] ) ? $plugin_data_item['PluginURI'] : '';
521
522 // Base slug from path.
523 $slug = dirname( $plugin_file );
524
525 // Single-file plugin at plugins root: dirname() returns '.'.
526 if ( '.' === $slug || '' === $slug ) {
527 $slug = basename( $plugin_file, '.php' );
528 }
529
530
531 // If PluginURI points to WordPress.org, prefer slug from the URI.
532 if ( ! empty( $uri ) && preg_match( '#WordPress\.org/plugins/([^/]+)/?#i', $uri, $m ) ) {
533 $slug = $m[1];
534 }
535
536 // Normalize slug similar to WP's sanitize_title().
537 $slug = sanitize_title( $slug );
538
539 $mu_indicator = '';
540 if ( 0 === strpos( $plugin_file, 'limit-login-attempts-reloaded' ) ) {
541 $mu_indicator = self::is_mu() ? ' MU' : '';
542 }
543
544 // Prefer official WordPress.org PluginURI when it is clearly such.
545 if ( ! empty( $uri ) && 0 === strpos( $uri, 'https://wordpress.org/plugins/' ) ) {
546 $debug_info .= $name . ' ' . $version . ' (' . $uri . ')' . $mu_indicator . "\n";
547 } else {
548 $debug_info .= $name . ' ' . $version . ' (https://wordpress.org/plugins/' . $slug . '/)' . $mu_indicator . "\n";
549 }
550 }
551 }
552 }
553
554 $current_theme = wp_get_theme();
555 $theme_name = is_object( $current_theme ) ? $current_theme->get( 'Name' ) : '';
556 $theme_uri = is_object( $current_theme ) ? $current_theme->get( 'ThemeURI' ) : '';
557 $theme_slug = is_object( $current_theme ) ? $current_theme->get_stylesheet() : '';
558
559 $debug_info .= "\nActive Theme:\n";
560
561 if ( ! empty( $theme_uri ) && 0 === strpos( $theme_uri, 'https://wordpress.org/themes/' ) ) {
562 $debug_info .= $theme_name . ' (' . $theme_uri . ')' . "\n";
563 } else {
564 $debug_info .= $theme_name . ' (https://wordpress.org/themes/' . $theme_slug . '/)' . "\n";
565 }
566
567 return $debug_info;
568 }
569
570 /**
571 * Write the plugin's header Version to options (for migrations / diagnostics after activate or in-dashboard update).
572 *
573 * @return void
574 */
575 public static function persist_stored_plugin_version() {
576 Config::init();
577 $plugin_data = get_plugin_data( LLA_PLUGIN_FILE, false, false );
578 $version = is_array( $plugin_data ) && ! empty( $plugin_data['Version'] )
579 ? sanitize_text_field( $plugin_data['Version'] )
580 : '';
581 Config::update( 'plugin_version', $version );
582 }
583
584 }