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; } }