PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 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 All 139 releases
← All changes | inc/Services/OpenAiService.php +318 -357 4.3.94.4.8 View file →
@@ -1,357 +1,318 @@
1 -<?php
2 -
3 -namespace LearnPress\Services;
4 -
5 -use Exception;
6 -use LearnPress\Helpers\Singleton;
7 -use LP_Helper;
8 -use LP_Settings;
9 -
10 -/**
11 - * Class OpenAiService
12 - *
13 - * Handles interactions with the OpenAI API for LearnPress.
14 - *
15 - * @package LearnPress\Services
16 - * @since 4.3.0
17 - * @version 1.0.0
18 - */
19 -class OpenAiService {
20 - use Singleton;
21 -
22 - public string $baseUrl = 'https://api.openai.com/v1/';
23 - public string $urlChartCompletion;
24 - public string $urlResponses;
25 - public string $urlCompletionLegacy;
26 - public string $urlImage;
27 -
28 - public string $secret_key;
29 - public string $text_model_type;
30 - public string $image_model_type;
31 - public float $frequency_penalty;
32 - public float $presence_penalty;
33 - public float $creativity_level;
34 - public int $max_token;
35 -
36 - public function init() {
37 - $this->urlChartCompletion = $this->baseUrl . 'chat/completions';
38 - $this->urlCompletionLegacy = $this->baseUrl . 'completions';
39 - $this->urlResponses = $this->baseUrl . 'responses';
40 - $this->urlImage = $this->baseUrl . 'images/generations';
41 - $this->get_settings();
42 - }
43 -
44 - /**
45 - * Check OpenAI integration is enabled
46 - */
47 - public function is_enable(): bool {
48 - return LP_Settings::get_option( 'enable_open_ai', 'no' ) === 'yes';
49 - }
50 -
51 - /**
52 - * Get secret key
53 - *
54 - * @return string
55 - * @since 4.3.6
56 - * @version 1.0.0
57 - */
58 - public function get_secret_key(): string {
59 - return (string) LP_Settings::get_option( 'open_ai_secret_key', '' );
60 - }
61 -
62 - public function get_settings() {
63 - $this->secret_key = LP_Settings::get_option( 'open_ai_secret_key', '' );
64 - $this->text_model_type = LP_Settings::get_option( 'open_ai_text_model_type', 'gpt-4.1' );
65 - $this->image_model_type = LP_Settings::get_option( 'open_ai_image_model_type', 'dall-e-3' );
66 - $this->frequency_penalty = LP_Settings::get_option( 'open_ai_frequency_penalty_level', 0.0 );
67 - $this->presence_penalty = LP_Settings::get_option( 'open_ai_presence_penalty_level', 0.0 );
68 - $this->creativity_level = LP_Settings::get_option( 'open_ai_creativity_level', 1.0 );
69 - $this->max_token = LP_Settings::get_option( 'open_ai_max_token', 0 );
70 - }
71 -
72 - /**
73 - * Call OpenAI API
74 - *
75 - * @throws Exception
76 - */
77 - public function send_request( array $args ): array {
78 - // Handle args before send request
79 - $url = '';
80 -
81 - if ( in_array(
82 - $this->text_model_type,
83 - [ 'chatgpt-4o-latest', 'gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo' ]
84 - ) ) {
85 - $url = $this->urlChartCompletion;
86 - $args = $this->handle_params_for_send_chat_completion( $args );
87 - } elseif ( $this->text_model_type == 'gpt-3.5-turbo-instruct' ) {
88 - $url = $this->urlCompletionLegacy;
89 - $args = $this->handle_params_for_send_completion_legacy( $args );
90 - } else {
91 - $url = $this->urlResponses;
92 - $args = $this->handle_params_for_send_responses( $args );
93 - }
94 -
95 - $response = wp_remote_post(
96 - $url,
97 - [
98 - 'headers' => [
99 - 'Authorization' => 'Bearer ' . $this->secret_key,
100 - 'Content-Type' => 'application/json',
101 - ],
102 - 'body' => json_encode( $args ),
103 - 'timeout' => 3600,
104 - ]
105 - );
106 -
107 - if ( is_wp_error( $response ) ) {
108 - throw new Exception( $response->get_error_message(), 400 );
109 - }
110 -
111 - $body = wp_remote_retrieve_body( $response );
112 - $data = LP_Helper::json_decode( $body, true );
113 - if ( isset( $data['error'] ) ) {
114 - throw new Exception( $data['error']['message'] );
115 - }
116 -
117 - return $this->detected_data( $data );
118 - }
119 -
120 - /**
121 - * Send a chat completion request and return the raw message object(s).
122 - *
123 - * Unlike send_request() which pipes through detected_data() (JSON-parsing
124 - * content into lp_structure_data), this method returns the raw
125 - * choices[].message objects so callers can handle tool_calls, content,
126 - * and role directly. Used by the AI Assistant agent loop.
127 - *
128 - * @param array $params Must include 'messages' array. May include 'tools', 'tool_choice'.
129 - *
130 - * @return array The first choice's message object (keys: role, content, tool_calls, etc.)
131 - * @throws Exception On API error or empty response.
132 - * @since 4.3.0
133 - */
134 - public function send_chat_request( array $params ): array {
135 - $args = $this->handle_params_for_send_chat_completion( $params );
136 -
137 - $response = wp_remote_post(
138 - $this->urlChartCompletion,
139 - [
140 - 'headers' => [
141 - 'Authorization' => 'Bearer ' . $this->secret_key,
142 - 'Content-Type' => 'application/json',
143 - ],
144 - 'body' => json_encode( $args ),
145 - 'timeout' => 3600,
146 - ]
147 - );
148 -
149 - if ( is_wp_error( $response ) ) {
150 - throw new Exception( $response->get_error_message(), 400 );
151 - }
152 -
153 - $body = wp_remote_retrieve_body( $response );
154 - $data = LP_Helper::json_decode( $body, true );
155 -
156 - if ( isset( $data['error'] ) ) {
157 - throw new Exception( $data['error']['message'] );
158 - }
159 -
160 - if ( empty( $data['choices'][0]['message'] ) ) {
161 - throw new Exception( __( 'Empty response from OpenAI API.', 'learnpress' ) );
162 - }
163 -
164 - $message = $data['choices'][0]['message'];
165 - if ( isset( $data['usage'] ) && is_array( $data['usage'] ) ) {
166 - $message['usage'] = $data['usage'];
167 - }
168 -
169 - return $message;
170 - }
171 -
172 - /**
173 - * @throws Exception
174 - */
175 - public function send_request_create_image( $args ) {
176 - $args_default = [
177 - 'model' => $this->image_model_type,
178 - 'prompt' => '',
179 - ];
180 -
181 - $args = array_merge( $args_default, $args );
182 -
183 - $model_type = LP_Settings::get_option( 'open_ai_image_model_type', 'gpt-image-1' );
184 - if ( $model_type === 'gpt-image-1' ) {
185 - unset( $args['n'] );
186 - }
187 -
188 - if ( $model_type == 'dall-e-3' ) {
189 - // only n=1 is supported.
190 - $args['n'] = 1;
191 - }
192 -
193 - $response = wp_remote_post(
194 - $this->urlImage,
195 - [
196 - 'headers' => [
197 - 'Authorization' => 'Bearer ' . $this->secret_key,
198 - 'Content-Type' => 'application/json',
199 - ],
200 - 'body' => json_encode( $args ),
201 - 'timeout' => 3600,
202 - ]
203 - );
204 -
205 - if ( is_wp_error( $response ) ) {
206 - throw new Exception( $response->get_error_message(), 400 );
207 - }
208 -
209 - $body = wp_remote_retrieve_body( $response );
210 - $data = LP_Helper::json_decode( $body, true );
211 - if ( isset( $data['error'] ) ) {
212 - throw new Exception( $data['error']['message'] );
213 - }
214 -
215 - return $data;
216 - }
217 -
218 - /**
219 - * Handle params for send chat completion
220 - *
221 - * @docs https://platform.openai.com/docs/api-reference/chat/create
222 - *
223 - * @throws Exception
224 - */
225 - public function handle_params_for_send_chat_completion( $params ): array {
226 - $has_tools = ! empty( $params['tools'] );
227 -
228 - // When caller supplies a pre-built messages array (e.g. multi-turn assistant),
229 - // use it as-is instead of rebuilding from a single prompt string.
230 - if ( ! empty( $params['messages'] ) && is_array( $params['messages'] ) ) {
231 - $messages = $params['messages'];
232 - } else {
233 - $messages = [
234 - [
235 - 'role' => 'system',
236 - 'content' => 'You are an AI assistant specialized in education and course design.',
237 - ],
238 - [
239 - 'role' => 'user',
240 - 'content' => $params['prompt'] ?? '',
241 - ],
242 - ];
243 - }
244 -
245 - $result = [
246 - 'model' => $this->text_model_type,
247 - 'frequency_penalty' => $this->frequency_penalty,
248 - 'presence_penalty' => $this->presence_penalty,
249 - 'temperature' => $this->creativity_level,
250 - 'max_completion_tokens' => $this->max_token,
251 - 'n' => $params['outputs'] ?? 1,
252 - 'messages' => $messages,
253 - ];
254 -
255 - // Only set response_format when tools are NOT present.
256 - // Forced json_object mode conflicts with tool_calls responses.
257 - if ( ! $has_tools ) {
258 - $result['response_format'] = [ 'type' => 'json_object' ];
259 - }
260 -
261 - if ( $has_tools ) {
262 - $result['tools'] = $params['tools'];
263 -
264 - if ( ! empty( $params['tool_choice'] ) ) {
265 - $result['tool_choice'] = $params['tool_choice'];
266 - }
267 - }
268 -
269 - if ( $this->max_token === 0 ) {
270 - unset( $result['max_completion_tokens'] );
271 - }
272 -
273 - return $result;
274 - }
275 -
276 - /**
277 - * Handle params for send chat completion
278 - *
279 - * @docs https://platform.openai.com/docs/api-reference/completions/create
280 - *
281 - * @throws Exception
282 - */
283 - public function handle_params_for_send_completion_legacy( $params ): array {
284 - $params = [
285 - 'model' => $this->text_model_type,
286 - 'temperature' => $this->creativity_level,
287 - 'max_tokens' => $this->max_token,
288 - 'n' => $params['outputs'] ?? 1,
289 - 'prompt' => $params['prompt'] ?? '',
290 - ];
291 -
292 - if ( $this->max_token === 0 ) {
293 - unset( $params['max_tokens'] );
294 - }
295 -
296 - return $params;
297 - }
298 -
299 - /**
300 - * Handle params for send chat completion
301 - *
302 - * @docs https://platform.openai.com/docs/api-reference/responses/create
303 - *
304 - * @throws Exception
305 - */
306 - public function handle_params_for_send_responses( $params ): array {
307 - $params = [
308 - 'model' => $this->text_model_type,
309 - //'temperature' => $this->creativity_level,
310 - 'max_output_tokens' => $this->max_token,
311 - 'input' => $params['prompt'] ?? '',
312 - ];
313 -
314 - if ( $this->max_token === 0 ) {
315 - unset( $params['max_output_tokens'] );
316 - }
317 -
318 - return $params;
319 - }
320 -
321 - /**
322 - * Detect data from response
323 - *
324 - * @throws Exception
325 - */
326 - public function detected_data( array $data ): array {
327 - $data['lp_structure_data'] = [];
328 -
329 - if ( isset( $data['choices'] ) ) {
330 - foreach ( $data['choices'] as $choice ) {
331 - $text = $choice['message']['content'] ?? $choice['text'] ?? '';
332 - $text = str_replace( [ "\\n", "\\r", "\n", "\r", "\t" ], '', $text );
333 -
334 - $data['lp_structure_data'][] = LP_Helper::json_decode( $text, true );
335 - }
336 - } elseif ( isset( $data['output'] ) ) {
337 - foreach ( $data['output'] as $output ) {
338 - $content_data = $output['content'] ?? [];
339 - if ( empty( $content_data ) ) {
340 - continue;
341 - }
342 -
343 - foreach ( $output['content'] as $content ) {
344 - try {
345 - $text = $content['text'] ?? '';
346 - $text = str_replace( [ "\\n", "\\r", "\n", "\r", "\t" ], '', $text );
347 - $data['lp_structure_data'][] = LP_Helper::json_decode( $text, true );
348 - } catch ( Exception $e ) {
349 - $data['lp_structure_data'][] = $content['text'] ?? '';
350 - }
351 - }
352 - }
353 - }
354 -
355 - return $data;
356 - }
357 -}
1 +<?php
2 +
3 +namespace LearnPress\Services;
4 +
5 +use Exception;
6 +use LearnPress\Helpers\Singleton;
7 +use LP_Helper;
8 +use LP_Settings;
9 +
10 +/**
11 + * Class OpenAiService
12 + *
13 + * Handles interactions with the OpenAI API for LearnPress.
14 + *
15 + * @package LearnPress\Services
16 + * @since 4.3.0
17 + * @version 1.0.1
18 + */
19 +class OpenAiService {
20 + use Singleton;
21 +
22 + public string $baseUrl = 'https://api.openai.com/v1/';
23 + public string $urlChartCompletion;
24 + public string $urlResponses;
25 + public string $urlImage;
26 +
27 + public string $secret_key;
28 + public string $text_model_type;
29 + public string $image_model_type;
30 + public float $frequency_penalty;
31 + public float $presence_penalty;
32 + public float $creativity_level;
33 + public int $max_token;
34 +
35 + public function init() {
36 + $this->urlChartCompletion = $this->baseUrl . 'chat/completions';
37 + $this->urlResponses = $this->baseUrl . 'responses';
38 + $this->urlImage = $this->baseUrl . 'images/generations';
39 + $this->get_settings();
40 + }
41 +
42 + /**
43 + * Check OpenAI integration is enabled
44 + */
45 + public function is_enable(): bool {
46 + return LP_Settings::get_option( 'enable_open_ai', 'no' ) === 'yes';
47 + }
48 +
49 + /**
50 + * Get secret key
51 + *
52 + * @return string
53 + * @since 4.3.6
54 + * @version 1.0.0
55 + */
56 + public function get_secret_key(): string {
57 + return (string) LP_Settings::get_option( 'open_ai_secret_key', '' );
58 + }
59 +
60 + public function get_settings() {
61 + $this->secret_key = LP_Settings::get_option( 'open_ai_secret_key', '' );
62 + $this->text_model_type = LP_Settings::get_option( 'open_ai_text_model_type', 'gpt-4.1' );
63 + $this->image_model_type = LP_Settings::get_option( 'open_ai_image_model_type', 'gpt-image-1' );
64 + $this->frequency_penalty = LP_Settings::get_option( 'open_ai_frequency_penalty_level', 0.0 );
65 + $this->presence_penalty = LP_Settings::get_option( 'open_ai_presence_penalty_level', 0.0 );
66 + $this->creativity_level = LP_Settings::get_option( 'open_ai_creativity_level', 1.0 );
67 + $this->max_token = LP_Settings::get_option( 'open_ai_max_token', 0 );
68 + }
69 +
70 + /**
71 + * Call OpenAI API
72 + *
73 + * @throws Exception
74 + */
75 + public function send_request( array $args ): array {
76 + $url = $this->urlResponses;
77 + $args = $this->handle_params_for_send_responses( $args );
78 +
79 + $response = wp_remote_post(
80 + $url,
81 + [
82 + 'headers' => [
83 + 'Authorization' => 'Bearer ' . $this->secret_key,
84 + 'Content-Type' => 'application/json',
85 + ],
86 + 'body' => json_encode( $args ),
87 + 'timeout' => 3600,
88 + ]
89 + );
90 +
91 + if ( is_wp_error( $response ) ) {
92 + throw new Exception( $response->get_error_message(), 400 );
93 + }
94 +
95 + $body = wp_remote_retrieve_body( $response );
96 + $data = LP_Helper::json_decode( $body, true );
97 + if ( isset( $data['error'] ) ) {
98 + throw new Exception( $data['error']['message'] );
99 + }
100 +
101 + return $this->detected_data( $data );
102 + }
103 +
104 + /**
105 + * Send a chat completion request and return the raw message object(s).
106 + *
107 + * Unlike send_request() which pipes through detected_data() (JSON-parsing
108 + * content into lp_structure_data), this method returns the raw
109 + * choices[].message objects so callers can handle tool_calls, content,
110 + * and role directly. Used by the AI Assistant agent loop.
111 + *
112 + * @param array $params Must include 'messages' array. May include 'tools', 'tool_choice'.
113 + *
114 + * @return array The first choice's message object (keys: role, content, tool_calls, etc.)
115 + * @throws Exception On API error or empty response.
116 + * @since 4.3.0
117 + */
118 + public function send_chat_request( array $params ): array {
119 + $args = $this->handle_params_for_send_chat_completion( $params );
120 +
121 + $response = wp_remote_post(
122 + $this->urlChartCompletion,
123 + [
124 + 'headers' => [
125 + 'Authorization' => 'Bearer ' . $this->secret_key,
126 + 'Content-Type' => 'application/json',
127 + ],
128 + 'body' => json_encode( $args ),
129 + 'timeout' => 3600,
130 + ]
131 + );
132 +
133 + if ( is_wp_error( $response ) ) {
134 + throw new Exception( $response->get_error_message(), 400 );
135 + }
136 +
137 + $body = wp_remote_retrieve_body( $response );
138 + $data = LP_Helper::json_decode( $body, true );
139 +
140 + if ( isset( $data['error'] ) ) {
141 + throw new Exception( $data['error']['message'] );
142 + }
143 +
144 + if ( empty( $data['choices'][0]['message'] ) ) {
145 + throw new Exception( __( 'Empty response from OpenAI API.', 'learnpress' ) );
146 + }
147 +
148 + $message = $data['choices'][0]['message'];
149 + if ( isset( $data['usage'] ) && is_array( $data['usage'] ) ) {
150 + $message['usage'] = $data['usage'];
151 + }
152 +
153 + return $message;
154 + }
155 +
156 + /**
157 + * @throws Exception
158 + */
159 + public function send_request_create_image( $args ) {
160 + $args_default = [
161 + 'model' => $this->image_model_type,
162 + 'prompt' => '',
163 + ];
164 +
165 + $args = array_merge( $args_default, $args );
166 +
167 + $model_type = LP_Settings::get_option( 'open_ai_image_model_type', 'gpt-image-1' );
168 + if ( $model_type === 'gpt-image-1' ) {
169 + unset( $args['n'] );
170 + }
171 +
172 + /*if ( $model_type == 'dall-e-3' ) {
173 + // only n=1 is supported.
174 + $args['n'] = 1;
175 + }*/
176 +
177 + $response = wp_remote_post(
178 + $this->urlImage,
179 + [
180 + 'headers' => [
181 + 'Authorization' => 'Bearer ' . $this->secret_key,
182 + 'Content-Type' => 'application/json',
183 + ],
184 + 'body' => json_encode( $args ),
185 + 'timeout' => 3600,
186 + ]
187 + );
188 +
189 + if ( is_wp_error( $response ) ) {
190 + throw new Exception( $response->get_error_message(), 400 );
191 + }
192 +
193 + $body = wp_remote_retrieve_body( $response );
194 + $data = LP_Helper::json_decode( $body, true );
195 + if ( isset( $data['error'] ) ) {
196 + throw new Exception( $data['error']['message'] );
197 + }
198 +
199 + return $data;
200 + }
201 +
202 + /**
203 + * Handle params for send chat completion
204 + *
205 + * @docs https://platform.openai.com/docs/api-reference/chat/create
206 + *
207 + * @throws Exception
208 + */
209 + public function handle_params_for_send_chat_completion( $params ): array {
210 + $has_tools = ! empty( $params['tools'] );
211 +
212 + // When caller supplies a pre-built messages array (e.g. multi-turn assistant),
213 + // use it as-is instead of rebuilding from a single prompt string.
214 + if ( ! empty( $params['messages'] ) && is_array( $params['messages'] ) ) {
215 + $messages = $params['messages'];
216 + } else {
217 + $messages = [
218 + [
219 + 'role' => 'system',
220 + 'content' => 'You are an AI assistant specialized in education and course design.',
221 + ],
222 + [
223 + 'role' => 'user',
224 + 'content' => $params['prompt'] ?? '',
225 + ],
226 + ];
227 + }
228 +
229 + $result = [
230 + 'model' => $this->text_model_type,
231 + 'frequency_penalty' => $this->frequency_penalty,
232 + 'presence_penalty' => $this->presence_penalty,
233 + 'temperature' => $this->creativity_level,
234 + 'max_completion_tokens' => $this->max_token,
235 + 'n' => $params['outputs'] ?? 1,
236 + 'messages' => $messages,
237 + ];
238 +
239 + // Only set response_format when tools are NOT present.
240 + // Forced json_object mode conflicts with tool_calls responses.
241 + if ( ! $has_tools ) {
242 + $result['response_format'] = [ 'type' => 'json_object' ];
243 + }
244 +
245 + if ( $has_tools ) {
246 + $result['tools'] = $params['tools'];
247 +
248 + if ( ! empty( $params['tool_choice'] ) ) {
249 + $result['tool_choice'] = $params['tool_choice'];
250 + }
251 + }
252 +
253 + if ( $this->max_token === 0 ) {
254 + unset( $result['max_completion_tokens'] );
255 + }
256 +
257 + return $result;
258 + }
259 +
260 + /**
261 + * Handle params for send chat completion
262 + *
263 + * @docs https://platform.openai.com/docs/api-reference/responses/create
264 + *
265 + * @throws Exception
266 + */
267 + public function handle_params_for_send_responses( $params ): array {
268 + $params = [
269 + 'model' => $this->text_model_type,
270 + //'temperature' => $this->creativity_level,
271 + 'max_output_tokens' => $this->max_token,
272 + 'input' => $params['prompt'] ?? '',
273 + ];
274 +
275 + if ( $this->max_token === 0 ) {
276 + unset( $params['max_output_tokens'] );
277 + }
278 +
279 + return $params;
280 + }
281 +
282 + /**
283 + * Detect data from response
284 + *
285 + * @throws Exception
286 + */
287 + public function detected_data( array $data ): array {
288 + $data['lp_structure_data'] = [];
289 +
290 + if ( isset( $data['choices'] ) ) {
291 + foreach ( $data['choices'] as $choice ) {
292 + $text = $choice['message']['content'] ?? $choice['text'] ?? '';
293 + $text = str_replace( [ "\\n", "\\r", "\n", "\r", "\t" ], '', $text );
294 +
295 + $data['lp_structure_data'][] = LP_Helper::json_decode( $text, true );
296 + }
297 + } elseif ( isset( $data['output'] ) ) {
298 + foreach ( $data['output'] as $output ) {
299 + $content_data = $output['content'] ?? [];
300 + if ( empty( $content_data ) ) {
301 + continue;
302 + }
303 +
304 + foreach ( $output['content'] as $content ) {
305 + try {
306 + $text = $content['text'] ?? '';
307 + $text = str_replace( [ "\\n", "\\r", "\n", "\r", "\t" ], '', $text );
308 + $data['lp_structure_data'][] = LP_Helper::json_decode( $text, true );
309 + } catch ( Exception $e ) {
310 + $data['lp_structure_data'][] = $content['text'] ?? '';
311 + }
312 + }
313 + }
314 + }
315 +
316 + return $data;
317 + }
318 +}