PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.1
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.1
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 All 138 releases
learnpress / inc / AI / Assistant / TokenQuotaGuard.php

TokenQuotaGuard.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.1, at inc/AI/Assistant/TokenQuotaGuard.php

212 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LearnPress\AI\Assistant;
4
5 use LearnPress\Services\OpenAiService;
6 use LP_Settings;
7
8 /**
9 * TokenQuotaGuard — per-user daily token quota tracking and enforcement.
10 *
11 * Wraps OpenAI chat requests with a quota check so that Agent.php is not
12 * responsible for any quota bookkeeping.
13 *
14 * @package LearnPress\AI\Assistant
15 * @since 4.3.5
16 */
17 class TokenQuotaGuard {
18
19 private const USER_META_DAILY_TOKEN_USAGE = '_lp_ai_assistant_daily_token_usage';
20
21 /**
22 * Human-readable block message set after the first exhausted-quota call.
23 */
24 private string $block_message = '';
25
26 /**
27 * Send an OpenAI chat request guarded by the daily token quota.
28 *
29 * On quota exhaustion the guard sets the block message and returns an
30 * empty array instead of calling the API.
31 *
32 * @param OpenAiService $service OpenAI service instance.
33 * @param array $messages Chat messages payload.
34 * @param int $user_id Current user ID.
35 *
36 * @return array OpenAI response, or empty array when blocked.
37 * @throws \Throwable Re-throws underlying OpenAI errors.
38 */
39 public function send_chat_with_guard( OpenAiService $service, array $messages, int $user_id ): array {
40
41 if ( $this->has_reached_daily_token_limit( $user_id ) ) {
42 $this->block_message = $this->build_block_message();
43 return array();
44 }
45
46 $response = $service->send_chat_request( array( 'messages' => $messages ) );
47 $this->track_token_usage_from_response( $user_id, $response );
48
49 if ( $this->has_reached_daily_token_limit( $user_id ) ) {
50 $this->block_message = $this->build_block_message();
51 }
52
53 return $response;
54 }
55
56 /**
57 * Whether the last send_chat_with_guard call was blocked by quota.
58 *
59 * @return bool
60 */
61 public function is_blocked(): bool {
62 return $this->block_message !== '';
63 }
64
65 /**
66 * Human-readable block message (empty string when not blocked).
67 *
68 * @return string
69 */
70 public function get_block_message(): string {
71 return $this->block_message;
72 }
73
74 /**
75 * Reset block state between runs.
76 *
77 * @return void
78 */
79 public function reset(): void {
80 $this->block_message = '';
81 }
82
83 // ----------------------------------------------------------------
84 // Private helpers
85 // ----------------------------------------------------------------
86
87 /**
88 * Track token usage from an OpenAI response usage payload.
89 *
90 * @param int $user_id Current user ID.
91 * @param array $response Raw OpenAI response.
92 *
93 * @return void
94 */
95 private function track_token_usage_from_response( int $user_id, array $response ): void {
96
97 $usage = $response['usage'] ?? array();
98 $total_tokens = absint( $usage['total_tokens'] ?? 0 );
99
100 if ( $total_tokens <= 0 ) {
101 return;
102 }
103
104 $this->increase_daily_token_usage( $user_id, $total_tokens );
105 }
106
107 /**
108 * Check whether the learner has reached their daily token limit.
109 *
110 * @param int $user_id Current user ID.
111 *
112 * @return bool
113 */
114 private function has_reached_daily_token_limit( int $user_id ): bool {
115
116 $limit = $this->get_daily_token_limit();
117 if ( $limit <= 0 ) {
118 return false;
119 }
120
121 return $this->get_daily_token_usage( $user_id ) >= $limit;
122 }
123
124 /**
125 * Get the configured daily token limit from plugin settings.
126 *
127 * @return int 0 means unlimited.
128 */
129 private function get_daily_token_limit(): int {
130 return absint( LP_Settings::get_option( 'ai_assistant_max_usage_tokens_per_day', 0 ) );
131 }
132
133 /**
134 * Read learner daily token usage from user meta.
135 *
136 * @param int $user_id Current user ID.
137 *
138 * @return int
139 */
140 private function get_daily_token_usage( int $user_id ): int {
141
142 if ( $user_id <= 0 ) {
143 return 0;
144 }
145
146 $payload = get_user_meta( $user_id, self::USER_META_DAILY_TOKEN_USAGE, true );
147 if ( ! is_array( $payload ) ) {
148 return 0;
149 }
150
151 $current_date = $this->get_local_current_date();
152 $stored_date = (string) ( $payload['date'] ?? '' );
153 if ( $stored_date !== $current_date ) {
154 return 0;
155 }
156
157 return absint( $payload['total_tokens'] ?? 0 );
158 }
159
160 /**
161 * Increase learner daily token usage and persist to user meta.
162 *
163 * @param int $user_id Current user ID.
164 * @param int $tokens Tokens consumed this call.
165 *
166 * @return void
167 */
168 private function increase_daily_token_usage( int $user_id, int $tokens ): void {
169
170 if ( $user_id <= 0 || $tokens <= 0 ) {
171 return;
172 }
173
174 $current_total = $this->get_daily_token_usage( $user_id );
175 $next_total = $current_total + $tokens;
176
177 update_user_meta(
178 $user_id,
179 self::USER_META_DAILY_TOKEN_USAGE,
180 array(
181 'date' => $this->get_local_current_date(),
182 'total_tokens' => $next_total,
183 )
184 );
185 }
186
187 /**
188 * Get local current date key for daily usage bucket.
189 *
190 * @return string
191 */
192 private function get_local_current_date(): string {
193 return (string) current_time( 'Y-m-d' );
194 }
195
196 /**
197 * Build user-facing quota exceeded message.
198 *
199 * @return string
200 */
201 private function build_block_message(): string {
202
203 $limit = $this->get_daily_token_limit();
204
205 return sprintf(
206 /* translators: %d: max usage tokens per learner per day. */
207 __( 'Daily AI usage limit reached (%d tokens). Please try again tomorrow or contact the site administrator.', 'learnpress' ),
208 $limit
209 );
210 }
211 }
212