get_component( 'geo_blocking' ); if ( $geo_blocking && $ip_address ) { $country_code = $geo_blocking->get_country_code( $ip_address ); } $table = WPSP_DB::get_log_table(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $result = $wpdb->insert( $table, 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' => '', 'orderby' => 'created_at', 'order' => 'DESC', ); $args = wp_parse_args( $args, $defaults ); $table = WPSP_DB::get_log_table(); $where_clauses = array( '1=1' ); $where_values = array(); if ( ! empty( $args['event_type'] ) ) { $where_clauses[] = 'event_type = %s'; $where_values[] = $args['event_type']; } if ( ! empty( $args['ip_address'] ) ) { $where_clauses[] = 'ip_address = %s'; $where_values[] = $args['ip_address']; } $where_sql = implode( ' AND ', $where_clauses ); // Sanitize orderby. $allowed_orderby = array( 'id', 'event_type', 'ip_address', 'username', 'created_at' ); $orderby = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'created_at'; $order = 'ASC' === strtoupper( $args['order'] ) ? 'ASC' : 'DESC'; $limit = absint( $args['limit'] ); $offset = absint( $args['offset'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d"; $where_values[] = $limit; $where_values[] = $offset; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared return $wpdb->get_results( $wpdb->prepare( $sql, $where_values ) ); } /** * Get total log count. * * @param array $args Query arguments. * @return int */ public static function get_log_count( $args = array() ) { global $wpdb; $table = WPSP_DB::get_log_table(); $where_clauses = array( '1=1' ); $where_values = array(); if ( ! empty( $args['event_type'] ) ) { $where_clauses[] = 'event_type = %s'; $where_values[] = $args['event_type']; } if ( ! empty( $args['ip_address'] ) ) { $where_clauses[] = 'ip_address = %s'; $where_values[] = $args['ip_address']; } $where_sql = implode( ' AND ', $where_clauses ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}"; if ( ! empty( $where_values ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared return (int) $wpdb->get_var( $wpdb->prepare( $sql, $where_values ) ); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared return (int) $wpdb->get_var( $sql ); } /** * Get log statistics. * * @param int $days Number of days to look back. * @return array */ public static function get_stats( $days = 30 ) { global $wpdb; $table = WPSP_DB::get_log_table(); $cutoff = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $results = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT event_type, COUNT(*) as count FROM {$table} 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 = WP_Security_Pack::get_setting( 'log_retention_days', 30 ); $cutoff = gmdate( 'Y-m-d H:i:s', strtotime( "-{$retention_days} days" ) ); $log_table = WPSP_DB::get_log_table(); $lockout_table = WPSP_DB::get_lockout_table(); // Delete old logs. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "DELETE FROM {$log_table} WHERE created_at < %s", $cutoff ) ); // Delete expired lockouts (lockout_until stores Unix timestamp as integer). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "DELETE FROM {$lockout_table} 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; $table = WPSP_DB::get_log_table(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared return false !== $wpdb->query( "TRUNCATE TABLE {$table}" ); } /** * 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', 'wp-security-pack' ), self::EVENT_LOGIN_FAILED => __( 'Login Failed', 'wp-security-pack' ), self::EVENT_LOCKOUT => __( 'Lockout', 'wp-security-pack' ), self::EVENT_IP_BLOCKED => __( 'IP Blocked', 'wp-security-pack' ), self::EVENT_GEO_BLOCKED => __( 'Geo Blocked', 'wp-security-pack' ), self::EVENT_LOCKOUT_LIFTED => __( 'Lockout Lifted', 'wp-security-pack' ), ); return isset( $labels[ $event_type ] ) ? $labels[ $event_type ] : $event_type; } }