user_login : ''; $user_role = $user_obj && ! empty( $user_obj->roles ) ? (string) $user_obj->roles[0] : ''; $severity = isset( $event['severity'] ) ? (string) $event['severity'] : 'info'; if ( ! in_array( $severity, self::SEVERITIES, true ) ) { $severity = 'info'; } $context = ''; if ( ! empty( $event['context'] ) ) { $context = is_string( $event['context'] ) ? $event['context'] : wp_json_encode( $event['context'] ); } $row = array( 'user_id' => $user_id, 'user_login' => sanitize_user( $user_login, true ), 'user_role' => sanitize_key( $user_role ), 'action_type' => sanitize_key( $event['action_type'] ), 'object_type' => isset( $event['object_type'] ) ? sanitize_key( $event['object_type'] ) : '', 'object_id' => isset( $event['object_id'] ) ? (int) $event['object_id'] : 0, 'object_title' => isset( $event['object_title'] ) ? sanitize_text_field( wp_strip_all_tags( (string) $event['object_title'] ) ) : '', 'description' => isset( $event['description'] ) ? wp_kses_post( (string) $event['description'] ) : '', 'context' => $context, 'severity' => $severity, 'ip_address' => self::get_client_ip(), 'user_agent' => self::get_user_agent(), 'session_id' => self::get_session_id(), 'created_at' => current_time( 'mysql' ), ); /** * Filter the row immediately before insertion. */ $row = APWPM_Compat::filter( 'apwpm_pre_insert_row', 'ap_pre_insert_row', $row, $event ); // Async batching: push onto the queue, defer DB write until shutdown. if ( class_exists( 'APWPM_Queue' ) && APWPM_Queue::enabled() && empty( $event['_force_sync'] ) ) { APWPM_Queue::push( $row ); APWPM_Compat::action( 'apwpm_event_queued', 'ap_event_queued', $row ); return 0; } $id = APWPM_DB::insert( $row ); if ( $id ) { /** * Fires after a row is recorded successfully. * The legacy `apwpm_logged` and `apwpm_event_logged` hooks are also fired. */ APWPM_Compat::action( 'apwpm_logged', 'ap_logged', $id, $row ); APWPM_Compat::action( 'apwpm_event_logged', 'ap_event_logged', $id, $row ); if ( in_array( $severity, array( 'error', 'critical' ), true ) ) { APWPM_Break_Detector::evaluate(); } if ( class_exists( 'APWPM_Insights' ) ) { APWPM_Insights::evaluate_row( $id, $row ); } } return $id; } /** * Best-effort client IP detection while respecting privacy. */ protected static function get_client_ip() { $candidates = array( 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' ); foreach ( $candidates as $key ) { if ( empty( $_SERVER[ $key ] ) ) { continue; } $value = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) ); if ( strpos( $value, ',' ) !== false ) { $value = trim( explode( ',', $value )[0] ); } if ( filter_var( $value, FILTER_VALIDATE_IP ) ) { return $value; } } return ''; } protected static function get_user_agent() { if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { return ''; } return substr( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ), 0, 255 ); } /** * Stable per-PHP-session identifier used to group activities by login session. * Falls back to a hash of user + IP + day when session is unavailable. */ protected static function get_session_id() { $uid = get_current_user_id(); $ip = self::get_client_ip(); $day = gmdate( 'Y-m-d' ); return substr( md5( $uid . '|' . $ip . '|' . $day ), 0, 32 ); } }