PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 1.9.4
AI Engine – The Chatbot, AI Framework & MCP for WordPress v1.9.4
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
620 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 new Meow_MWAI_Modules_Security( $this );
42 if ( $this->is_rest ) {
43 new Meow_MWAI_Rest( $this );
44 }
45 if ( is_admin() ) {
46 new Meow_MWAI_Admin( $this );
47 new Meow_MWAI_Modules_Assistants( $this );
48 }
49 if ( $this->get_option( 'shortcode_chat' ) ) {
50 $chatbot_module = new Meow_MWAI_Modules_Chatbot();
51 new Meow_MWAI_Modules_Chatbot_Legacy();
52 new Meow_MWAI_Modules_Discussions();
53 }
54
55 // Advanced core
56 if ( class_exists( 'MeowPro_MWAI_Core' ) ) {
57 new MeowPro_MWAI_Core( $this );
58 }
59
60 // Dynamic max tokens
61 if ( $this->get_option( 'dynamic_max_tokens' ) ) {
62 add_filter( 'mwai_estimate_tokens', array( $this, 'dynamic_max_tokens' ), 10, 2 );
63 }
64
65 $mwai = new Meow_MWAI_API( $chatbot_module );
66 }
67
68 #region Roles & Capabilities
69
70 function can_access_settings() {
71 return apply_filters( 'mwai_allow_setup', current_user_can( 'manage_options' ) );
72 }
73
74 function can_access_features() {
75 $editor_or_admin = current_user_can( 'editor' ) || current_user_can( 'administrator' );
76 return apply_filters( 'mwai_allow_usage', $editor_or_admin );
77 }
78
79 function can_access_public_api( $feature, $extra ) {
80 $logged_in = is_user_logged_in();
81 return apply_filters( 'mwai_allow_public_api', $logged_in, $feature, $extra );
82 }
83
84 #endregion
85
86 #region Text-Related Helpers
87
88 // Clean the text perfectly, resolve shortcodes, etc, etc.
89 function cleanText( $rawText = "" ) {
90 $text = html_entity_decode( $rawText );
91 $text = wp_strip_all_tags( $text );
92 $text = preg_replace( '/[\r\n]+/', "\n", $text );
93 return $text . " ";
94
95 // Before simplification:
96 // $text = strip_tags( $rawText );
97 // $text = strip_shortcodes( $text );
98 // $text = html_entity_decode( $text );
99 // $text = preg_replace( '/[\r\n]+/', "\n", $text );
100 // $sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
101 // foreach ( $sentences as $key => $sentence ) {
102 // $sentences[$key] = trim( $sentence );
103 // }
104 // $text = implode( " ", $sentences );
105 // $text = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $text );
106 // return $text . " ";
107 }
108
109 // Make sure there are no duplicate sentences, and keep the length under a maximum length.
110 function cleanSentences( $text, $maxTokens = null ) {
111 //$sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
112 $maxTokens = $maxTokens ? $maxTokens : $this->get_option( 'context_max_tokens', 1024 );
113 $sentences = preg_split('/(?<=[.?!。.!?])+/u', $text);
114 $hashes = array();
115 $uniqueSentences = array();
116 $length = 0;
117 foreach ( $sentences as $sentence ) {
118 $sentence = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $sentence );
119 $hash = md5( $sentence );
120 if ( !in_array( $hash, $hashes ) ) {
121 $tokensCount = apply_filters( 'mwai_estimate_tokens', 0, $sentence );
122 if ( $length + $tokensCount > $maxTokens ) {
123 continue;
124 }
125 $hashes[] = $hash;
126 $uniqueSentences[] = $sentence;
127 $length += $tokensCount;
128 }
129 }
130 $freshText = implode( " ", $uniqueSentences );
131 $freshText = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $freshText );
132 return $freshText;
133 }
134
135 function getCleanPostContent( $postId ) {
136 $post = get_post( $postId );
137 if ( !$post ) {
138 return false;
139 }
140 $text = apply_filters( 'mwai_pre_post_content', $post->post_content, $postId );
141 $pattern = '/\[mwai_.*?\]/';
142 $text = preg_replace( $pattern, '', $text );
143 if ( $this->get_option( 'resolve_shortcodes' ) ) {
144 $text = apply_filters( 'the_content', $text );
145 }
146 else {
147 $pattern = "/\[[^\]]+\]/";
148 $text = preg_replace( $pattern, '', $text );
149 $pattern = "/<!--\s*\/?wp:[^\>]+-->/";
150 $text = preg_replace( $pattern, '', $text );
151 }
152 $text = $this->cleanText( $text );
153 $text = $this->cleanSentences( $text );
154 $text = apply_filters( 'mwai_post_content', $text, $postId );
155 return $text;
156 }
157
158 function markdown_to_html( $content ) {
159 $Parsedown = new Parsedown();
160 $content = $Parsedown->text( $content );
161 return $content;
162 }
163
164 function get_post_language( $postId ) {
165 $locale = get_locale();
166 $code = strtolower( substr( $locale, 0, 2 ) );
167 $humanLanguage = strtr( $code, MWAI_ALL_LANGUAGES );
168 $lang = apply_filters( 'wpml_post_language_details', null, $postId );
169 if ( !empty( $lang ) ) {
170 $locale = $lang['locale'];
171 $humanLanguage = $lang['display_name'];
172 }
173 return strtolower( "$locale ($humanLanguage)" );
174 }
175 #endregion
176
177 #region Users/Sessions Helpers
178
179 function get_nonce() {
180 if ( !is_user_logged_in() ) {
181 return null;
182 }
183 if ( isset( $this->nonce ) ) {
184 return $this->nonce;
185 }
186 $this->nonce = wp_create_nonce( 'wp_rest' );
187 return $this->nonce;
188 }
189
190 function get_session_id() {
191 if ( isset( $_COOKIE['mwai_session_id'] ) ) {
192 return $_COOKIE['mwai_session_id'];
193 }
194 return "N/A";
195 }
196
197 // Get the UserID from the data, or from the current user
198 function get_user_id( $data = null ) {
199 if ( isset( $data ) && isset( $data['userId'] ) ) {
200 return (int)$data['userId'];
201 }
202 if ( is_user_logged_in() ) {
203 $current_user = wp_get_current_user();
204 if ( $current_user->ID > 0 ) {
205 return $current_user->ID;
206 }
207 }
208 return null;
209 }
210
211 function getUserData() {
212 $user = wp_get_current_user();
213 if ( empty( $user ) || empty( $user->ID ) ) {
214 return null;
215 }
216 $placeholders = array(
217 'FIRST_NAME' => get_user_meta( $user->ID, 'first_name', true ),
218 'LAST_NAME' => get_user_meta( $user->ID, 'last_name', true ),
219 'USER_LOGIN' => isset( $user ) && isset($user->data) && isset( $user->data->user_login ) ?
220 $user->data->user_login : null,
221 'DISPLAY_NAME' => isset( $user ) && isset( $user->data ) && isset( $user->data->display_name ) ?
222 $user->data->display_name : null,
223 'AVATAR_URL' => get_avatar_url( get_current_user_id() ),
224 );
225 return $placeholders;
226 }
227
228 function get_ip_address( $data = null ) {
229 if ( isset( $data ) && isset( $data['ip'] ) ) {
230 $data['ip'] = (string)$data['ip'];
231 }
232 else {
233 if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
234 $data['ip'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
235 }
236 else if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
237 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_CLIENT_IP'] );
238 }
239 else if ( isset( $_SERVER['HTTP_X_FORWARDED_ FOR'] ) ) {
240 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_X_FORWARDED_FOR'] );
241 }
242 }
243 $ip = apply_filters( 'mwai_get_ip_address', $data['ip'] );
244 return $ip;
245 }
246
247 #endregion
248
249 #region Other Helpers
250
251 function isUrl( $url ) {
252 return strpos( $url, 'http' ) === 0 ? true : false;
253 }
254
255 function getPostTypes() {
256 $excluded = array( 'attachment', 'revision', 'nav_menu_item' );
257 $post_types = array();
258 $types = get_post_types( [], 'objects' );
259
260 // Let's get the Post Types that are enabled for Embeddings Sync
261 $embeddingsSettings = $this->get_option( 'embeddings' );
262 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
263
264 foreach ( $types as $type ) {
265 $forced = in_array( $type->name, $syncPostTypes );
266 // Should not be excluded.
267 if ( !$forced && in_array( $type->name, $excluded ) ) {
268 continue;
269 }
270 // Should be public.
271 if ( !$forced && !$type->public ) {
272 continue;
273 }
274 $post_types[] = array(
275 'name' => $type->labels->name,
276 'type' => $type->name,
277 );
278 }
279
280 // Let's get the Post Types that are enabled for Embeddings Sync
281 $embeddingsSettings = $this->get_option( 'embeddings' );
282 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
283
284 return $post_types;
285 }
286
287 function getCleanPost( $post ) {
288 if ( is_object( $post ) ) {
289 $post = (array)$post;
290 }
291 $language = $this->get_post_language( $post['ID'] );
292 $content = $this->getCleanPostContent( $post['ID'] );
293 $title = $post['post_title'];
294 $excerpt = $post['post_excerpt'];
295 $url = get_permalink( $post['ID'] );
296 $checksum = wp_hash( $content . $title . $url );
297 return [
298 'postId' => $post['ID'],
299 'title' => $title,
300 'content' => $content,
301 'excerpt' => $excerpt,
302 'url' => $url,
303 'language' => $language,
304 'checksum' => $checksum,
305 ];
306 }
307 #endregion
308
309 #region Usage & Costs
310
311 public function dynamic_max_tokens( $tokens, $text ) {
312 // Approximation (fast, no lib)
313 $asciiCount = 0;
314 $nonAsciiCount = 0;
315 for ( $i = 0; $i < mb_strlen( $text ); $i++ ) {
316 $char = mb_substr( $text, $i, 1 );
317 if ( ord( $char ) < 128 ) {
318 $asciiCount++;
319 }
320 else {
321 $nonAsciiCount++;
322 }
323 }
324 $asciiTokens = $asciiCount / 3.5;
325 $nonAsciiTokens = $nonAsciiCount * 2.5;
326 $tokens = $asciiTokens + $nonAsciiTokens;
327
328 // More exact (slower, and lib)
329 if ( PHP_VERSION_ID >= 70400 && function_exists( 'mb_convert_encoding' ) ) {
330 try {
331 $token_array = Encoder::encode( $text );
332 if ( !empty( $token_array ) ) {
333 $tokens = count( $token_array );
334 }
335 }
336 catch ( Exception $e ) {
337 error_log( $e->getMessage() );
338 }
339 }
340
341 $tokens = $tokens;
342 return (int)$tokens;
343 }
344
345 public function recordTokensUsage( $model, $prompt_tokens, $completion_tokens = 0 ) {
346 if ( !is_numeric( $prompt_tokens ) ) {
347 throw new Exception( 'Record usage: prompt_tokens is not a number.' );
348 }
349 if ( !is_numeric( $completion_tokens ) ) {
350 $completion_tokens = 0;
351 }
352 if ( !$model ) {
353 throw new Exception( 'Record usage: model is missing.' );
354 }
355 $usage = $this->get_option( 'openai_usage' );
356 $month = date( 'Y-m' );
357 if ( !isset( $usage[$month] ) ) {
358 $usage[$month] = array();
359 }
360 if ( !isset( $usage[$month][$model] ) ) {
361 $usage[$month][$model] = array(
362 'prompt_tokens' => 0,
363 'completion_tokens' => 0,
364 'total_tokens' => 0
365 );
366 }
367 $usage[$month][$model]['prompt_tokens'] += $prompt_tokens;
368 $usage[$month][$model]['completion_tokens'] += $completion_tokens;
369 $usage[$month][$model]['total_tokens'] += $prompt_tokens + $completion_tokens;
370 $this->update_option( 'openai_usage', $usage );
371 return [
372 'prompt_tokens' => $prompt_tokens,
373 'completion_tokens' => $completion_tokens,
374 'total_tokens' => $prompt_tokens + $completion_tokens
375 ];
376 }
377
378 public function recordAudioUsage( $model, $seconds ) {
379 if ( !is_numeric( $seconds ) ) {
380 throw new Exception( 'Record usage: seconds is not a number.' );
381 }
382 if ( !$model ) {
383 throw new Exception( 'Record usage: model is missing.' );
384 }
385 $usage = $this->get_option( 'openai_usage' );
386 $month = date( 'Y-m' );
387 if ( !isset( $usage[$month] ) ) {
388 $usage[$month] = array();
389 }
390 if ( !isset( $usage[$month][$model] ) ) {
391 $usage[$month][$model] = array(
392 'seconds' => 0
393 );
394 }
395 $usage[$month][$model]['seconds'] += $seconds;
396 $this->update_option( 'openai_usage', $usage );
397 return [
398 'seconds' => $seconds
399 ];
400 }
401
402 public function recordImagesUsage( $model, $resolution, $images ) {
403 if ( !$model || !$resolution || !$images ) {
404 throw new Exception( 'Missing parameters for record_image_usage.' );
405 }
406 $usage = $this->get_option( 'openai_usage' );
407 $month = date( 'Y-m' );
408 if ( !isset( $usage[$month] ) ) {
409 $usage[$month] = array();
410 }
411 if ( !isset( $usage[$month][$model] ) ) {
412 $usage[$month][$model] = array(
413 'resolution' => array(),
414 'images' => 0
415 );
416 }
417 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
418 $usage[$month][$model]['resolution'][$resolution] = 0;
419 }
420 $usage[$month][$model]['resolution'][$resolution] += $images;
421 $usage[$month][$model]['images'] += $images;
422 $this->update_option( 'openai_usage', $usage );
423 return [
424 'resolution' => $resolution,
425 'images' => $images
426 ];
427 }
428
429 #endregion
430
431 #region Streaming
432 public function stream_push( $data ) {
433 $out = "data: " . json_encode( $data );
434 echo $out;
435 echo "\n\n";
436 if ( ob_get_level() > 0 ) {
437 ob_end_flush();
438 }
439 flush();
440 }
441 #endregion
442
443 #region Options
444 function getThemes() {
445 $themes = get_option( $this->themes_option_name, [] );
446 $themes = empty( $themes ) ? [] : $themes;
447
448 $internalThemes = [
449 'chatgpt' => [
450 'type' => 'internal', 'name' => 'ChatGPT', 'themeId' => 'chatgpt',
451 'settings' => [], 'style' => ""
452 ],
453 'messages' => [
454 'type' => 'internal', 'name' => 'Messages', 'themeId' => 'messages',
455 'settings' => [], 'style' => ""
456 ],
457 ];
458 $customThemes = [];
459 foreach ( $themes as $theme ) {
460 if ( isset( $internalThemes[$theme['themeId']] ) ) {
461 $internalThemes[$theme['themeId']] = $theme;
462 continue;
463 }
464 $customThemes[] = $theme;
465 }
466 return array_merge(array_values($internalThemes), $customThemes);
467 }
468
469 function updateThemes( $themes ) {
470 update_option( $this->themes_option_name, $themes );
471 return $themes;
472 }
473
474 function getChatbots() {
475 $chatbots = get_option( $this->chatbots_option_name, [] );
476 $hasChanges = false;
477 if ( empty( $chatbots ) ) {
478 $chatbots = [ array_merge( MWAI_CHATBOT_DEFAULT_PARAMS, ['name' => 'Default', 'botId' => 'default' ] ) ];
479 }
480 foreach ( $chatbots as &$chatbot ) {
481 foreach ( MWAI_CHATBOT_DEFAULT_PARAMS as $key => $value ) {
482 // Use default value if not set.
483 if ( !isset( $chatbot[$key] ) ) {
484 $chatbot[$key] = $value;
485 }
486 }
487 // After September 2023, let's remove this if statement.
488 if ( isset( $chatbot['chatId'] ) ) {
489 $chatbot['botId'] = $chatbot['chatId'];
490 unset( $chatbot['chatId'] );
491 $hasChanges = true;
492 }
493 // After September 2023, let's remove this if statement.
494 if ( empty( $chatbot['botId'] ) && $chatbot['name'] === 'default' ) {
495 $chatbot['botId'] = sanitize_title( $chatbot['name'] );
496 $hasChanges = true;
497 }
498 }
499 if ( $hasChanges ) {
500 update_option( $this->chatbots_option_name, $chatbots );
501 }
502 return $chatbots;
503 }
504
505 function getChatbot( $botId ) {
506 $chatbots = $this->getChatbots();
507 foreach ( $chatbots as $chatbot ) {
508 if ( $chatbot['botId'] === (string)$botId ) {
509 // Somehow, the default was set to "openai" when creating a new chatbot, but that overrided
510 // the default value in the Settings. It should be always empty here (except if we add this
511 // into the Settings of the chatbot).
512 $chatbot['service'] = null;
513 return $chatbot;
514 }
515 }
516 return null;
517 }
518
519 function getTheme( $themeId ) {
520 $themes = $this->getThemes();
521 foreach ( $themes as $theme ) {
522 if ( $theme['themeId'] === $themeId ) {
523 return $theme;
524 }
525 }
526 return null;
527 }
528
529 function updateChatbots( $chatbots ) {
530 $htmlFields = [ 'textCompliance', 'aiName', 'userName', 'startSentence' ];
531 $whiteSpacedFields = [ 'context' ];
532 foreach ( $chatbots as &$chatbot ) {
533 foreach ( $chatbot as $key => &$value ) {
534 if ( in_array( $key, $htmlFields ) ) {
535 $value = wp_kses_post( $value );
536 }
537 else if ( in_array( $key, $whiteSpacedFields ) ) {
538 $value = sanitize_textarea_field( $value );
539 }
540 else {
541 $value = sanitize_text_field( $value );
542 }
543 }
544 }
545
546 update_option( $this->chatbots_option_name, $chatbots );
547 return $chatbots;
548 }
549
550 function get_all_options( $force = false ) {
551 // We could cache options this way, but if we do, the apply_filters seems to be called too early.
552 // That causes issues with the mwai_languages filter.
553 // if ( !$force && !is_null( $this->options ) ) {
554 // return $this->options;
555 // }
556 $options = get_option( $this->option_name, [] );
557 $options = $this->sanitize_options( $options );
558 foreach ( MWAI_OPTIONS as $key => $value ) {
559 if ( !isset( $options[$key] ) ) {
560 $options[$key] = $value;
561 }
562 if ( $key === 'languages' ) {
563 // NOTE: If we decide to make a set of options for languages, we can keep it in the settings
564 $options[$key] = apply_filters( 'mwai_languages', MWAI_LANGUAGES );
565 }
566 }
567 $options['shortcode_chat_default_params'] = MWAI_CHATBOT_PARAMS;
568 $options['chatbot_defaults'] = MWAI_CHATBOT_DEFAULT_PARAMS;
569 $options['default_limits'] = MWAI_LIMITS;
570 $options['openai_models'] = Meow_MWAI_Engines_OpenAI::get_openai_models();
571 $this->options = $options;
572 return $options;
573 }
574
575 // Sanitize options when we update the plugi or perform some updates
576 // if we change the structure of the options.
577 function sanitize_options( $options ) {
578 $needs_update = false;
579
580 // This upgrades namespace to multi-namespaces (June 2023)
581 // After January 2024, let's remove this.
582 if ( isset( $options['pinecone'] ) && isset( $options['pinecone']['namespace'] ) ) {
583 $options['pinecone']['namespaces'] = [ $options['pinecone']['namespace'] ];
584 unset( $options['pinecone']['namespace'] );
585 $needs_update = true;
586 }
587
588 if ( $needs_update ) {
589 update_option( $this->option_name, $options, false );
590 }
591
592 return $options;
593 }
594
595 function update_options( $options ) {
596 if ( !update_option( $this->option_name, $options, false ) ) {
597 return false;
598 }
599 $options = $this->get_all_options( true );
600 return $options;
601 }
602
603 function update_option( $option, $value ) {
604 $options = $this->get_all_options( true );
605 $options[$option] = $value;
606 return $this->update_options( $options );
607 }
608
609 function get_option( $option, $default = null ) {
610 $options = $this->get_all_options();
611 return $options[$option] ?? $default;
612 }
613
614 function reset_options() {
615 return $this->update_options( MWAI_OPTIONS );
616 }
617 #endregion
618 }
619
620 ?>