mirror of
https://gitlab.com/ArkHost/WP-Security-Pack.git
synced 2026-09-19 17:37:30 +02:00
v1.0
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
/**
|
||||
* Activity logging for Security Pack.
|
||||
*
|
||||
* @package Security_Pack
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity log class.
|
||||
*/
|
||||
class WPSP_Activity_Log {
|
||||
|
||||
/**
|
||||
* Event types.
|
||||
*/
|
||||
const EVENT_LOGIN_SUCCESS = 'login_success';
|
||||
const EVENT_LOGIN_FAILED = 'login_failed';
|
||||
const EVENT_LOCKOUT = 'lockout';
|
||||
const EVENT_IP_BLOCKED = 'ip_blocked';
|
||||
const EVENT_GEO_BLOCKED = 'geo_blocked';
|
||||
const EVENT_LOCKOUT_LIFTED = 'lockout_lifted';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Hooks are set up by Login Protection class.
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an event.
|
||||
*
|
||||
* @param string $event_type Event type.
|
||||
* @param string|null $ip_address IP address (auto-detected if null).
|
||||
* @param string|null $username Username.
|
||||
* @param string|null $details Additional details.
|
||||
* @return int|false Insert ID or false on failure.
|
||||
*/
|
||||
public static function log( $event_type, $ip_address = null, $username = null, $details = null ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( null === $ip_address ) {
|
||||
$ip_address = WPSP_Helper::get_client_ip();
|
||||
}
|
||||
|
||||
// Get user agent.
|
||||
$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] )
|
||||
? substr( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ), 0, 255 )
|
||||
: '';
|
||||
|
||||
// Get country code if geo-blocking is available.
|
||||
$country_code = null;
|
||||
$geo_blocking = wpsp()->get_component( 'geo_blocking' );
|
||||
if ( $geo_blocking && $ip_address ) {
|
||||
$country_code = $geo_blocking->get_country_code( $ip_address );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$result = $wpdb->insert(
|
||||
$wpdb->prefix . 'wpsp_activity_log',
|
||||
array(
|
||||
'event_type' => $event_type,
|
||||
'ip_address' => $ip_address ? $ip_address : '',
|
||||
'username' => $username,
|
||||
'user_agent' => $user_agent,
|
||||
'country_code' => $country_code,
|
||||
'details' => $details,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
|
||||
);
|
||||
|
||||
return $result ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent logs.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_logs( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
'event_type' => '',
|
||||
'ip_address' => '',
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
$limit = absint( $args['limit'] );
|
||||
$offset = absint( $args['offset'] );
|
||||
|
||||
// Build query based on filters. Always order by created_at DESC for security logs.
|
||||
if ( ! empty( $args['event_type'] ) && ! empty( $args['ip_address'] ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}wpsp_activity_log WHERE event_type = %s AND ip_address = %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||||
$args['event_type'],
|
||||
$args['ip_address'],
|
||||
$limit,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
} elseif ( ! empty( $args['event_type'] ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}wpsp_activity_log WHERE event_type = %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||||
$args['event_type'],
|
||||
$limit,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
} elseif ( ! empty( $args['ip_address'] ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}wpsp_activity_log WHERE ip_address = %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||||
$args['ip_address'],
|
||||
$limit,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// No filters.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}wpsp_activity_log ORDER BY created_at DESC LIMIT %d OFFSET %d",
|
||||
$limit,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total log count.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
* @return int
|
||||
*/
|
||||
public static function get_log_count( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
// Build query based on filters.
|
||||
if ( ! empty( $args['event_type'] ) && ! empty( $args['ip_address'] ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->prefix}wpsp_activity_log WHERE event_type = %s AND ip_address = %s",
|
||||
$args['event_type'],
|
||||
$args['ip_address']
|
||||
)
|
||||
);
|
||||
} elseif ( ! empty( $args['event_type'] ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->prefix}wpsp_activity_log WHERE event_type = %s",
|
||||
$args['event_type']
|
||||
)
|
||||
);
|
||||
} elseif ( ! empty( $args['ip_address'] ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->prefix}wpsp_activity_log WHERE ip_address = %s",
|
||||
$args['ip_address']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// No filters.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security data must be real-time.
|
||||
return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wpsp_activity_log" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log statistics.
|
||||
*
|
||||
* @param int $days Number of days to look back.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_stats( $days = 30 ) {
|
||||
global $wpdb;
|
||||
|
||||
$cutoff = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Security stats must be real-time.
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT event_type, COUNT(*) as count FROM {$wpdb->prefix}wpsp_activity_log WHERE created_at >= %s GROUP BY event_type",
|
||||
$cutoff
|
||||
)
|
||||
);
|
||||
|
||||
$stats = array(
|
||||
'login_success' => 0,
|
||||
'login_failed' => 0,
|
||||
'lockout' => 0,
|
||||
'ip_blocked' => 0,
|
||||
'geo_blocked' => 0,
|
||||
'total' => 0,
|
||||
);
|
||||
|
||||
foreach ( $results as $row ) {
|
||||
$stats[ $row->event_type ] = (int) $row->count;
|
||||
$stats['total'] += (int) $row->count;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup old logs (cron job).
|
||||
*/
|
||||
public static function cleanup_old_logs() {
|
||||
global $wpdb;
|
||||
|
||||
$retention_days = Security_Pack::get_setting( 'log_retention_days', 30 );
|
||||
$cutoff = gmdate( 'Y-m-d H:i:s', strtotime( "-{$retention_days} days" ) );
|
||||
|
||||
// Delete old logs.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cleanup operation, caching not applicable.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->prefix}wpsp_activity_log WHERE created_at < %s",
|
||||
$cutoff
|
||||
)
|
||||
);
|
||||
|
||||
// Delete expired lockouts (lockout_until stores Unix timestamp as integer).
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cleanup operation, caching not applicable.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->prefix}wpsp_lockouts WHERE lockout_until IS NOT NULL AND lockout_until > 0 AND lockout_until < %d",
|
||||
time()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all logs.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function clear_all_logs() {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Truncate for admin action.
|
||||
return false !== $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}wpsp_activity_log" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event type label.
|
||||
*
|
||||
* @param string $event_type Event type.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_label( $event_type ) {
|
||||
$labels = array(
|
||||
self::EVENT_LOGIN_SUCCESS => __( 'Login Success', 'security-pack' ),
|
||||
self::EVENT_LOGIN_FAILED => __( 'Login Failed', 'security-pack' ),
|
||||
self::EVENT_LOCKOUT => __( 'Lockout', 'security-pack' ),
|
||||
self::EVENT_IP_BLOCKED => __( 'IP Blocked', 'security-pack' ),
|
||||
self::EVENT_GEO_BLOCKED => __( 'Geo Blocked', 'security-pack' ),
|
||||
self::EVENT_LOCKOUT_LIFTED => __( 'Lockout Lifted', 'security-pack' ),
|
||||
);
|
||||
|
||||
return isset( $labels[ $event_type ] ) ? $labels[ $event_type ] : $event_type;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user