PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 1.9.7
AI Engine – The Chatbot, AI Framework & MCP for WordPress v1.9.7
3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / core.php
ai-engine / classes Last commit date
engines 2 years ago modules 2 years ago queries 2 years ago admin.php 2 years ago api.php 2 years ago core.php 2 years ago init.php 3 years ago reply.php 2 years ago rest.php 2 years ago
core.php
621 lines
1 <?php
2
3 require_once( MWAI_PATH . '/vendor/autoload.php' );
4 require_once( MWAI_PATH . '/constants/init.php' );
5
6 use Rahul900day\Gpt3Encoder\Encoder;
7
8 define( 'MWAI_IMG_WAND', MWAI_URL . '/images/wand.png' );
9 define( 'MWAI_IMG_WAND_HTML', "<img style='height: 22px; margin-bottom: -5px; margin-right: 8px;'
10 src='" . MWAI_IMG_WAND . "' alt='AI Wand' />" );
11 define( 'MWAI_IMG_WAND_HTML_XS', "<img style='height: 16px; margin-bottom: -2px;'
12 src='" . MWAI_IMG_WAND . "' alt='AI Wand' />" );
13
14 class Meow_MWAI_Core
15 {
16 public $admin = null;
17 public $is_rest = false;
18 public $is_cli = false;
19 public $site_url = null;
20 public $ai = null;
21 private $option_name = 'mwai_options';
22 private $themes_option_name = 'mwai_themes';
23 private $chatbots_option_name = 'mwai_chatbots';
24 private $nonce = null;
25 public $defaultChatbotParams = MWAI_CHATBOT_PARAMS;
26
27 // Cached
28 private $options = null;
29
30 public function __construct() {
31 $this->site_url = get_site_url();
32 $this->is_rest = MeowCommon_Helpers::is_rest();
33 $this->is_cli = defined( 'WP_CLI' );
34 $this->ai = new Meow_MWAI_Engines_Core( $this );
35 add_action( 'plugins_loaded', array( $this, 'init' ) );
36 }
37
38 function init() {
39 global $mwai;
40 $chatbot_module = null;
41 $discussions_module = null;
42 new Meow_MWAI_Modules_Security( $this );
43 if ( $this->is_rest ) {
44 new Meow_MWAI_Rest( $this );
45 }
46 if ( is_admin() ) {
47 new Meow_MWAI_Admin( $this );
48 new Meow_MWAI_Modules_Assistants( $this );
49 }
50 if ( $this->get_option( 'shortcode_chat' ) ) {
51 $chatbot_module = new Meow_MWAI_Modules_Chatbot();
52 $discussions_module = new Meow_MWAI_Modules_Discussions();
53 // new Meow_MWAI_Modules_Chatbot_Legacy();
54 }
55
56 // Advanced core
57 if ( class_exists( 'MeowPro_MWAI_Core' ) ) {
58 new MeowPro_MWAI_Core( $this );
59 }
60
61 // Dynamic max tokens
62 if ( $this->get_option( 'dynamic_max_tokens' ) ) {
63 add_filter( 'mwai_estimate_tokens', array( $this, 'dynamic_max_tokens' ), 10, 2 );
64 }
65
66 $mwai = new Meow_MWAI_API( $chatbot_module, $discussions_module );
67 }
68
69 #region Roles & Capabilities
70
71 function can_access_settings() {
72 return apply_filters( 'mwai_allow_setup', current_user_can( 'manage_options' ) );
73 }
74
75 function can_access_features() {
76 $editor_or_admin = current_user_can( 'editor' ) || current_user_can( 'administrator' );
77 return apply_filters( 'mwai_allow_usage', $editor_or_admin );
78 }
79
80 function can_access_public_api( $feature, $extra ) {
81 $logged_in = is_user_logged_in();
82 return apply_filters( 'mwai_allow_public_api', $logged_in, $feature, $extra );
83 }
84
85 #endregion
86
87 #region Text-Related Helpers
88
89 // Clean the text perfectly, resolve shortcodes, etc, etc.
90 function cleanText( $rawText = "" ) {
91 $text = html_entity_decode( $rawText );
92 $text = wp_strip_all_tags( $text );
93 $text = preg_replace( '/[\r\n]+/', "\n", $text );
94 return $text . " ";
95
96 // Before simplification:
97 // $text = strip_tags( $rawText );
98 // $text = strip_shortcodes( $text );
99 // $text = html_entity_decode( $text );
100 // $text = preg_replace( '/[\r\n]+/', "\n", $text );
101 // $sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
102 // foreach ( $sentences as $key => $sentence ) {
103 // $sentences[$key] = trim( $sentence );
104 // }
105 // $text = implode( " ", $sentences );
106 // $text = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $text );
107 // return $text . " ";
108 }
109
110 // Make sure there are no duplicate sentences, and keep the length under a maximum length.
111 function cleanSentences( $text, $maxTokens = null ) {
112 //$sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
113 $maxTokens = $maxTokens ? $maxTokens : $this->get_option( 'context_max_tokens', 1024 );
114 $sentences = preg_split('/(?<=[.?!。.!?])+/u', $text);
115 $hashes = array();
116 $uniqueSentences = array();
117 $length = 0;
118 foreach ( $sentences as $sentence ) {
119 $sentence = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $sentence );
120 $hash = md5( $sentence );
121 if ( !in_array( $hash, $hashes ) ) {
122 $tokensCount = apply_filters( 'mwai_estimate_tokens', 0, $sentence );
123 if ( $length + $tokensCount > $maxTokens ) {
124 continue;
125 }
126 $hashes[] = $hash;
127 $uniqueSentences[] = $sentence;
128 $length += $tokensCount;
129 }
130 }
131 $freshText = implode( " ", $uniqueSentences );
132 $freshText = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $freshText );
133 return $freshText;
134 }
135
136 function getCleanPostContent( $postId ) {
137 $post = get_post( $postId );
138 if ( !$post ) {
139 return false;
140 }
141 $text = apply_filters( 'mwai_pre_post_content', $post->post_content, $postId );
142 $pattern = '/\[mwai_.*?\]/';
143 $text = preg_replace( $pattern, '', $text );
144 if ( $this->get_option( 'resolve_shortcodes' ) ) {
145 $text = apply_filters( 'the_content', $text );
146 }
147 else {
148 $pattern = "/\[[^\]]+\]/";
149 $text = preg_replace( $pattern, '', $text );
150 $pattern = "/<!--\s*\/?wp:[^\>]+-->/";
151 $text = preg_replace( $pattern, '', $text );
152 }
153 $text = $this->cleanText( $text );
154 $text = $this->cleanSentences( $text );
155 $text = apply_filters( 'mwai_post_content', $text, $postId );
156 return $text;
157 }
158
159 function markdown_to_html( $content ) {
160 $Parsedown = new Parsedown();
161 $content = $Parsedown->text( $content );
162 return $content;
163 }
164
165 function get_post_language( $postId ) {
166 $locale = get_locale();
167 $code = strtolower( substr( $locale, 0, 2 ) );
168 $humanLanguage = strtr( $code, MWAI_ALL_LANGUAGES );
169 $lang = apply_filters( 'wpml_post_language_details', null, $postId );
170 if ( !empty( $lang ) ) {
171 $locale = $lang['locale'];
172 $humanLanguage = $lang['display_name'];
173 }
174 return strtolower( "$locale ($humanLanguage)" );
175 }
176 #endregion
177
178 #region Users/Sessions Helpers
179
180 function get_nonce() {
181 if ( !is_user_logged_in() ) {
182 return null;
183 }
184 if ( isset( $this->nonce ) ) {
185 return $this->nonce;
186 }
187 $this->nonce = wp_create_nonce( 'wp_rest' );
188 return $this->nonce;
189 }
190
191 function get_session_id() {
192 if ( isset( $_COOKIE['mwai_session_id'] ) ) {
193 return $_COOKIE['mwai_session_id'];
194 }
195 return "N/A";
196 }
197
198 // Get the UserID from the data, or from the current user
199 function get_user_id( $data = null ) {
200 if ( isset( $data ) && isset( $data['userId'] ) ) {
201 return (int)$data['userId'];
202 }
203 if ( is_user_logged_in() ) {
204 $current_user = wp_get_current_user();
205 if ( $current_user->ID > 0 ) {
206 return $current_user->ID;
207 }
208 }
209 return null;
210 }
211
212 function getUserData() {
213 $user = wp_get_current_user();
214 if ( empty( $user ) || empty( $user->ID ) ) {
215 return null;
216 }
217 $placeholders = array(
218 'FIRST_NAME' => get_user_meta( $user->ID, 'first_name', true ),
219 'LAST_NAME' => get_user_meta( $user->ID, 'last_name', true ),
220 'USER_LOGIN' => isset( $user ) && isset($user->data) && isset( $user->data->user_login ) ?
221 $user->data->user_login : null,
222 'DISPLAY_NAME' => isset( $user ) && isset( $user->data ) && isset( $user->data->display_name ) ?
223 $user->data->display_name : null,
224 'AVATAR_URL' => get_avatar_url( get_current_user_id() ),
225 );
226 return $placeholders;
227 }
228
229 function get_ip_address( $data = null ) {
230 if ( isset( $data ) && isset( $data['ip'] ) ) {
231 $data['ip'] = (string)$data['ip'];
232 }
233 else {
234 if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
235 $data['ip'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
236 }
237 else if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
238 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_CLIENT_IP'] );
239 }
240 else if ( isset( $_SERVER['HTTP_X_FORWARDED_ FOR'] ) ) {
241 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_X_FORWARDED_FOR'] );
242 }
243 }
244 $ip = apply_filters( 'mwai_get_ip_address', $data['ip'] );
245 return $ip;
246 }
247
248 #endregion
249
250 #region Other Helpers
251
252 function isUrl( $url ) {
253 return strpos( $url, 'http' ) === 0 ? true : false;
254 }
255
256 function getPostTypes() {
257 $excluded = array( 'attachment', 'revision', 'nav_menu_item' );
258 $post_types = array();
259 $types = get_post_types( [], 'objects' );
260
261 // Let's get the Post Types that are enabled for Embeddings Sync
262 $embeddingsSettings = $this->get_option( 'embeddings' );
263 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
264
265 foreach ( $types as $type ) {
266 $forced = in_array( $type->name, $syncPostTypes );
267 // Should not be excluded.
268 if ( !$forced && in_array( $type->name, $excluded ) ) {
269 continue;
270 }
271 // Should be public.
272 if ( !$forced && !$type->public ) {
273 continue;
274 }
275 $post_types[] = array(
276 'name' => $type->labels->name,
277 'type' => $type->name,
278 );
279 }
280
281 // Let's get the Post Types that are enabled for Embeddings Sync
282 $embeddingsSettings = $this->get_option( 'embeddings' );
283 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
284
285 return $post_types;
286 }
287
288 function getCleanPost( $post ) {
289 if ( is_object( $post ) ) {
290 $post = (array)$post;
291 }
292 $language = $this->get_post_language( $post['ID'] );
293 $content = $this->getCleanPostContent( $post['ID'] );
294 $title = $post['post_title'];
295 $excerpt = $post['post_excerpt'];
296 $url = get_permalink( $post['ID'] );
297 $checksum = wp_hash( $content . $title . $url );
298 return [
299 'postId' => $post['ID'],
300 'title' => $title,
301 'content' => $content,
302 'excerpt' => $excerpt,
303 'url' => $url,
304 'language' => $language,
305 'checksum' => $checksum,
306 ];
307 }
308 #endregion
309
310 #region Usage & Costs
311
312 public function dynamic_max_tokens( $tokens, $text ) {
313 // Approximation (fast, no lib)
314 $asciiCount = 0;
315 $nonAsciiCount = 0;
316 for ( $i = 0; $i < mb_strlen( $text ); $i++ ) {
317 $char = mb_substr( $text, $i, 1 );
318 if ( ord( $char ) < 128 ) {
319 $asciiCount++;
320 }
321 else {
322 $nonAsciiCount++;
323 }
324 }
325 $asciiTokens = $asciiCount / 3.5;
326 $nonAsciiTokens = $nonAsciiCount * 2.5;
327 $tokens = $asciiTokens + $nonAsciiTokens;
328
329 // More exact (slower, and lib)
330 if ( PHP_VERSION_ID >= 70400 && function_exists( 'mb_convert_encoding' ) ) {
331 try {
332 $token_array = Encoder::encode( $text );
333 if ( !empty( $token_array ) ) {
334 $tokens = count( $token_array );
335 }
336 }
337 catch ( Exception $e ) {
338 error_log( $e->getMessage() );
339 }
340 }
341
342 $tokens = $tokens;
343 return (int)$tokens;
344 }
345
346 public function recordTokensUsage( $model, $prompt_tokens, $completion_tokens = 0 ) {
347 if ( !is_numeric( $prompt_tokens ) ) {
348 throw new Exception( 'Record usage: prompt_tokens is not a number.' );
349 }
350 if ( !is_numeric( $completion_tokens ) ) {
351 $completion_tokens = 0;
352 }
353 if ( !$model ) {
354 throw new Exception( 'Record usage: model is missing.' );
355 }
356 $usage = $this->get_option( 'openai_usage' );
357 $month = date( 'Y-m' );
358 if ( !isset( $usage[$month] ) ) {
359 $usage[$month] = array();
360 }
361 if ( !isset( $usage[$month][$model] ) ) {
362 $usage[$month][$model] = array(
363 'prompt_tokens' => 0,
364 'completion_tokens' => 0,
365 'total_tokens' => 0
366 );
367 }
368 $usage[$month][$model]['prompt_tokens'] += $prompt_tokens;
369 $usage[$month][$model]['completion_tokens'] += $completion_tokens;
370 $usage[$month][$model]['total_tokens'] += $prompt_tokens + $completion_tokens;
371 $this->update_option( 'openai_usage', $usage );
372 return [
373 'prompt_tokens' => $prompt_tokens,
374 'completion_tokens' => $completion_tokens,
375 'total_tokens' => $prompt_tokens + $completion_tokens
376 ];
377 }
378
379 public function recordAudioUsage( $model, $seconds ) {
380 if ( !is_numeric( $seconds ) ) {
381 throw new Exception( 'Record usage: seconds is not a number.' );
382 }
383 if ( !$model ) {
384 throw new Exception( 'Record usage: model is missing.' );
385 }
386 $usage = $this->get_option( 'openai_usage' );
387 $month = date( 'Y-m' );
388 if ( !isset( $usage[$month] ) ) {
389 $usage[$month] = array();
390 }
391 if ( !isset( $usage[$month][$model] ) ) {
392 $usage[$month][$model] = array(
393 'seconds' => 0
394 );
395 }
396 $usage[$month][$model]['seconds'] += $seconds;
397 $this->update_option( 'openai_usage', $usage );
398 return [
399 'seconds' => $seconds
400 ];
401 }
402
403 public function recordImagesUsage( $model, $resolution, $images ) {
404 if ( !$model || !$resolution || !$images ) {
405 throw new Exception( 'Missing parameters for record_image_usage.' );
406 }
407 $usage = $this->get_option( 'openai_usage' );
408 $month = date( 'Y-m' );
409 if ( !isset( $usage[$month] ) ) {
410 $usage[$month] = array();
411 }
412 if ( !isset( $usage[$month][$model] ) ) {
413 $usage[$month][$model] = array(
414 'resolution' => array(),
415 'images' => 0
416 );
417 }
418 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
419 $usage[$month][$model]['resolution'][$resolution] = 0;
420 }
421 $usage[$month][$model]['resolution'][$resolution] += $images;
422 $usage[$month][$model]['images'] += $images;
423 $this->update_option( 'openai_usage', $usage );
424 return [
425 'resolution' => $resolution,
426 'images' => $images
427 ];
428 }
429
430 #endregion
431
432 #region Streaming
433 public function stream_push( $data ) {
434 $out = "data: " . json_encode( $data );
435 echo $out;
436 echo "\n\n";
437 if ( ob_get_level() > 0 ) {
438 ob_end_flush();
439 }
440 flush();
441 }
442 #endregion
443
444 #region Options
445 function getThemes() {
446 $themes = get_option( $this->themes_option_name, [] );
447 $themes = empty( $themes ) ? [] : $themes;
448
449 $internalThemes = [
450 'chatgpt' => [
451 'type' => 'internal', 'name' => 'ChatGPT', 'themeId' => 'chatgpt',
452 'settings' => [], 'style' => ""
453 ],
454 'messages' => [
455 'type' => 'internal', 'name' => 'Messages', 'themeId' => 'messages',
456 'settings' => [], 'style' => ""
457 ],
458 ];
459 $customThemes = [];
460 foreach ( $themes as $theme ) {
461 if ( isset( $internalThemes[$theme['themeId']] ) ) {
462 $internalThemes[$theme['themeId']] = $theme;
463 continue;
464 }
465 $customThemes[] = $theme;
466 }
467 return array_merge(array_values($internalThemes), $customThemes);
468 }
469
470 function updateThemes( $themes ) {
471 update_option( $this->themes_option_name, $themes );
472 return $themes;
473 }
474
475 function getChatbots() {
476 $chatbots = get_option( $this->chatbots_option_name, [] );
477 $hasChanges = false;
478 if ( empty( $chatbots ) ) {
479 $chatbots = [ array_merge( MWAI_CHATBOT_DEFAULT_PARAMS, ['name' => 'Default', 'botId' => 'default' ] ) ];
480 }
481 foreach ( $chatbots as &$chatbot ) {
482 foreach ( MWAI_CHATBOT_DEFAULT_PARAMS as $key => $value ) {
483 // Use default value if not set.
484 if ( !isset( $chatbot[$key] ) ) {
485 $chatbot[$key] = $value;
486 }
487 }
488 // After September 2023, let's remove this if statement.
489 if ( isset( $chatbot['chatId'] ) ) {
490 $chatbot['botId'] = $chatbot['chatId'];
491 unset( $chatbot['chatId'] );
492 $hasChanges = true;
493 }
494 // After September 2023, let's remove this if statement.
495 if ( empty( $chatbot['botId'] ) && $chatbot['name'] === 'default' ) {
496 $chatbot['botId'] = sanitize_title( $chatbot['name'] );
497 $hasChanges = true;
498 }
499 }
500 if ( $hasChanges ) {
501 update_option( $this->chatbots_option_name, $chatbots );
502 }
503 return $chatbots;
504 }
505
506 function getChatbot( $botId ) {
507 $chatbots = $this->getChatbots();
508 foreach ( $chatbots as $chatbot ) {
509 if ( $chatbot['botId'] === (string)$botId ) {
510 // Somehow, the default was set to "openai" when creating a new chatbot, but that overrided
511 // the default value in the Settings. It should be always empty here (except if we add this
512 // into the Settings of the chatbot).
513 $chatbot['service'] = null;
514 return $chatbot;
515 }
516 }
517 return null;
518 }
519
520 function getTheme( $themeId ) {
521 $themes = $this->getThemes();
522 foreach ( $themes as $theme ) {
523 if ( $theme['themeId'] === $themeId ) {
524 return $theme;
525 }
526 }
527 return null;
528 }
529
530 function updateChatbots( $chatbots ) {
531 $htmlFields = [ 'textCompliance', 'aiName', 'userName', 'startSentence' ];
532 $whiteSpacedFields = [ 'context' ];
533 foreach ( $chatbots as &$chatbot ) {
534 foreach ( $chatbot as $key => &$value ) {
535 if ( in_array( $key, $htmlFields ) ) {
536 $value = wp_kses_post( $value );
537 }
538 else if ( in_array( $key, $whiteSpacedFields ) ) {
539 $value = sanitize_textarea_field( $value );
540 }
541 else {
542 $value = sanitize_text_field( $value );
543 }
544 }
545 }
546
547 update_option( $this->chatbots_option_name, $chatbots );
548 return $chatbots;
549 }
550
551 function get_all_options( $force = false ) {
552 // We could cache options this way, but if we do, the apply_filters seems to be called too early.
553 // That causes issues with the mwai_languages filter.
554 // if ( !$force && !is_null( $this->options ) ) {
555 // return $this->options;
556 // }
557 $options = get_option( $this->option_name, [] );
558 $options = $this->sanitize_options( $options );
559 foreach ( MWAI_OPTIONS as $key => $value ) {
560 if ( !isset( $options[$key] ) ) {
561 $options[$key] = $value;
562 }
563 if ( $key === 'languages' ) {
564 // NOTE: If we decide to make a set of options for languages, we can keep it in the settings
565 $options[$key] = apply_filters( 'mwai_languages', MWAI_LANGUAGES );
566 }
567 }
568 $options['shortcode_chat_default_params'] = MWAI_CHATBOT_PARAMS;
569 $options['chatbot_defaults'] = MWAI_CHATBOT_DEFAULT_PARAMS;
570 $options['default_limits'] = MWAI_LIMITS;
571 $options['openai_models'] = Meow_MWAI_Engines_OpenAI::get_openai_models();
572 $this->options = $options;
573 return $options;
574 }
575
576 // Sanitize options when we update the plugi or perform some updates
577 // if we change the structure of the options.
578 function sanitize_options( $options ) {
579 $needs_update = false;
580
581 // This upgrades namespace to multi-namespaces (June 2023)
582 // After January 2024, let's remove this.
583 if ( isset( $options['pinecone'] ) && isset( $options['pinecone']['namespace'] ) ) {
584 $options['pinecone']['namespaces'] = [ $options['pinecone']['namespace'] ];
585 unset( $options['pinecone']['namespace'] );
586 $needs_update = true;
587 }
588
589 if ( $needs_update ) {
590 update_option( $this->option_name, $options, false );
591 }
592
593 return $options;
594 }
595
596 function update_options( $options ) {
597 if ( !update_option( $this->option_name, $options, false ) ) {
598 return false;
599 }
600 $options = $this->get_all_options( true );
601 return $options;
602 }
603
604 function update_option( $option, $value ) {
605 $options = $this->get_all_options( true );
606 $options[$option] = $value;
607 return $this->update_options( $options );
608 }
609
610 function get_option( $option, $default = null ) {
611 $options = $this->get_all_options();
612 return $options[$option] ?? $default;
613 }
614
615 function reset_options() {
616 return $this->update_options( MWAI_OPTIONS );
617 }
618 #endregion
619 }
620
621 ?>